信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。信号量不一定是锁定某一个资源,而是流程上的概念,比如:有 A,B 两个线程,B 线程要等 A 线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。
信号量(信号灯)与互斥锁和条件变量的主要不同在于” 灯” 的概念,灯亮则意味着资源可用,灯灭则意味着不可用。信号量主要阻塞线程,不能完全保证线程安全,如果要保证线程安全,需要信号量和互斥锁一起使用。
信号量和条件变量一样用于处理生产者和消费者模型,用于阻塞生产者线程或者消费者线程的运行。
信号的类型为 sem_t 对应的头文件为 <semaphore.h>,sem_t sem;
// 初始化信号量/信号灯 int sem_init(sem_t *sem, int pshared, unsigned int value); // 资源释放, 线程销毁之后调用这个函数即可 // 参数 sem 就是 sem_init() 的第一个参数 int sem_destroy(sem_t *sem); 参数: sem:信号量变量地址 pshared: 0:线程同步 非 0:进程同步 value:初始化当前信号量拥有的资源数(>=0),如果资源数为 0,线程就会被阻塞了。
// 参数 sem 就是 sem_init() 的第一个参数 // 函数被调用sem中的资源就会被消耗1个, 资源数-1 int sem_wait(sem_t *sem); 当线程调用这个函数,并且 sem 中的资源数 >0,线程不会阻塞,线程会占用 sem 中的一个资源,因此资源数 - 1,直到 sem 中的资源数减为 0 时,资源被耗尽,因此线程也就被阻塞了。
/ 参数 sem 就是 sem_init() 的第一个参数 // 函数被调用sem中的资源就会被消耗1个, 资源数-1 int sem_trywait(sem_t *sem); 当线程调用这个函数,并且 sem 中的资源数 >0,线程不会阻塞,线程会占用 sem 中的一个资源,因此资源数 - 1,直到 sem 中的资源数减为 0 时,资源被耗尽,但是线程不会被阻塞,直接返回错误号,因此可以在程序中添加判断分支,用于处理获取资源失败之后的情况。
// 调用该函数给sem中的资源数+1 int sem_post(sem_t *sem); 调用该函数会将 sem 中的资源数 +1,如果有线程在调用 sem_wait、sem_trywait、sem_timedwait 时因为 sem 中的资源数为 0 被阻塞了,这时这些线程会解除阻塞,获取到资源之后继续向下运行。
// 查看信号量 sem 中的整形数的当前值, 这个值会被写入到sval指针对应的内存中 // sval是一个传出参数 int sem_getvalue(sem_t *sem, int *sval); 通过这个函数可以查看 sem 中现在拥有的资源个数,通过第二个参数 sval 将数据传出,也就是说第二个参数的作用和返回值是一样的。
如果生产者和消费者使用的信号量总资源数为 1,那么不会出现生产者线程和消费者线程同时访问共享资源的情况,不管生产者和消费者线程有多少个,它们都是顺序执行的。
如果生产者和消费者线程使用的信号量对应的总资源数为大于 1,这种场景下出现的情况就比较多了:
1)多个生产者线程同时生产 2)多个消费者同时消费 3)生产者线程和消费者线程同时生产和消费 以上不管哪一种情况都可能会出现多个线程访问共享资源的情况,如果想防止共享资源出现数据混乱,那么就需要使用互斥锁进行线程同步。
注意避免死锁
// 消费者 sem_wait(&csem); pthread_mutex_lock(&mutex);
// 生产者 sem_wait(&csem); pthread_mutex_lock(&mutex); 这两行代码的调用顺序是不能颠倒的,如果颠倒过来就有可能会造成死锁。初始化状态下消费者线程没有任务信号量资源,假设某一个消费者线程先运行,调用 pthread_mutex_lock(&mutex); 对互斥锁加锁成功,然后调用 sem_wait(&csem); 由于没有资源,因此被阻塞了。其余的消费者线程由于没有抢到互斥锁,因此被阻塞在互斥锁上。对应生产者线程第一步操作也是调用 pthread_mutex_lock(&mutex);,但是这时候互斥锁已经被消费者线程锁上了,所有生产者都被阻塞,到此为止,多余的线程都被阻塞了,程序产生了死锁。
部分内容引自:https://www.subingwen.cn/linux/thread-sync/#6-%E4%BF%A1%E5%8F%B7%E9%87%8F