当多个线程共享相同的内存时,需要确保每个线程看到一致的数据。如果每个线程使用的变量都是其他线程不会读取或修改的,那么就不存在一致性问题。同样地,如果变量是只读的,多个线程读取该变量也不会有一致性问题。但是,当某个线程可以修改变量,而其他线程也可以读取或者修改这个变量的时候,就需要对这些线程进行同步,以确保它们在访问变量的存储内容时不会访问到无效的数值。
信号量通常有两种:二进制信号量和计数信号量。二进制信号量只有0和1两种取值,计数信号量有更大的取值范围。
信号量一般用来保护一段代码,使其每次只能被一个执行线程运行,要完成这个工作,可以使用二进制信号量。
有时,希望可以允许有限数目的线程执行一段指定的代码,这时可以使用计数信号量。
信号量的创建
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化一个未命名的信号量(unnamed semaphore)。
- sem指向需要初始化的信号量(sem_t类型)。
- value指定信号量的初始值。
- pshared表明信号量是在一个进程的多个线程之间共享还是在多个进程之间共享。若pshared为0,信号量被一个进程的多个线程共享,此时应该将信号量(sem_t)置于所有线程可见的位置(全局变量或动态分配)。
执行成功返回0,出错返回-1,并设置errno。
注意:初始化一个已经初始化了的信号量将导致未定义的行为。
信号量控制
#include <semaphore.h>
int sem_post(sem_t *sem); // v
int sem_wait(sem_t *sem); // p
sem_post的作用是以原子操作的方式给信号量的值加1
sem_wait函数以原子操作的方式将信号量的值减1,但它会等待直到信号量有个非零值才会开始减法操作。例如,对值为2的信号量调用sem_wait,线程将继续执行,但信号量的值会减到1。如果对值为0的信号量调用sem_wait,这个函数就会等待,直到有其它线程增加了该信号量的值使其不再为0为止。
如果两个线程同时在sem_wait函数上等待同一个信号量变为非零值,那么当该信号量被第三个线程增加1时,只有其中一个等待线程将开始对信号量减1,然后继续执行,另外一个线程还将继续等待。
补充:还有另外一个信号量函数sem_trywait,它是sem_wait的非阻塞版本。
信号量销毁
#include <semaphore.h>
int sem_destroy(sem_t *sem);
这个函数的作用是,用完信号量后对它进行清理,清理该信号量所拥有的资源。如果你试图清理的信号量正被一些线程等待,就会收到一个错误。
生产者/消费者模型
多线程并发应用程序有一个经典的模型,即生产者/消费者模型。系统中,产生消息的是生产者,处理消息的是消费者,消费者和生产者通过一个缓冲区进行消息传递。生产者产生消息后提交到缓冲区,然后通知消费者可以从中取出消息进行处理。消费者处理完信息后,通知生产者可以继续提供消息。
要实现这个模型,关键在于消费者和生产者这两个线程进行同步。也就是说:只有缓冲区中有消息时,消费者才能够提取消息;只有消息已被处理,生产者才能产生消息提交到缓冲区。