信号量也属于一种进程间通信的机制,与其他的进程间通信不同,信号量不是用来传输数据的,而是用来进程间同步与互斥。除此之外,信号量还可以实现线程间的互斥。
信号量是什么?
信号量的本质是一个计数器。
一个信号量是由一个内核维护的整数,它的值被限制在大于等于0。对信号量可以进行以下操作:
1、将信号量设置一个值。
2、在信号量当前值的基础上加上一个数量(V操作)。
3、在信号量当前值的基础上减去一个数量(P操作)。
4、当信号量的值为0时,会被阻塞。
既然是计数器,那么其中的count也是临界资源,所以PV操作必须得是原子的。
Linux下信号量的P.V操作如何保证其原子性_weixin_34402090的博客-CSDN博客
P操作
相当于count--;
P(){lock();if(count)count--;elsewait(); // 阻塞unlock();
}
V操作
相当于count++
V(){lock();count++;if(count<=0)wakeup();//唤醒unlock();
}
注意:V操作的唤醒进程或者线程是不确定的,即具体是哪个进程或者线程被唤醒并允许递减这个信号量是不确定的,仅仅是一个同步机制,而不是一个排队机制。
POSIX信号量
头文件 semaphore.h
1、初始化一个信号量
sem_init函数
sem_t 声明一个信号量;
sem_init函数使用value中指定的值对sem指向的信号量进行初始化。通俗一点就是给信号量中的count值赋一个初值。
第二个参数要注意
pshared表明这个信号量是在线程间共享还是在进程间共享。
如果pshared=0,这个信号量会在调用进程中的线程间进行共享。此时这个信号量应该为临界资源,让线程都可以看到。
如果pshared=1,这个信号量在进程间共享。此时需要放在内存共享区,即共享内存或者mmap映射区域。
2、P操作
sem_wait()函数

sem_wait函数就是让sem指向的信号量的值减1.
如果信号量的当前值大于0,则sem_wait立即返回。如果信号量的当前值小于0,那么sem_wait会阻塞到信号量的值大于0为止。
3、V操作
sem_post函数

sem_post函数就是将sem指向的信号量的值+1。
如果在sem_post调用之前信号量的值为0,并且其他某个进程(线程)正在因为等待递减这个信号量而阻塞,那么该进程(线程)会被唤醒。
4、销毁一个信号量
sem_destroy函数

sem必须被sem_init初始化才能被销毁。
只有在不存在进程或线程在等待一个信号量时才能够安全销毁这个信号量。
用信号量实现线程互斥
用信号量来代替互斥锁mutex。
思想:将信号量设置为0,1两个状态,这样就可以达到互斥锁的效果。
//一个简单的抢票程序
#include <iostream>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
using namespace std;
class myclass
{
private:sem_t mtx;int ticket = 5000;
public:myclass(){sem_init(&mtx, 0, 1);//设置大小为1即和锁一样的特点}~myclass(){sem_destroy(&mtx);}void P()//封装一下PV操作的函数{sem_wait(&mtx);}void V(){sem_post(&mtx);}void Run(){pthread_t tid[4];for (int i = 0; i < 4; i++)//创建四个线程pthread_create(tid + i, nullptr, Routine, this);for (int i = 0; i < 4; i++)pthread_join(tid[i], nullptr);}static void* Routine(void* arg){myclass* ptr = (myclass*)arg;while (1){ptr->P();if (ptr->ticket > 0){usleep(1000);cout << "i am thread " << pthread_self() << ", i get tikcet num is " << ptr->ticket << endl;(ptr->ticket)--;ptr->V();}else{ptr->V();break;}}return nullptr;}
};
int main()
{myclass mc;mc.Run();return 0;
}














