信号量————生产者与消费者模型(环形缓冲区)

来源:互联网 发布:佳能戴尔超六淘宝 编辑:程序博客网 时间:2024/06/06 00:55

一. 信号量

    在进程间通信中有一种通信机制是信号量,它往往标识着一个临界资源的有无来控制不同的进程是否能访问到这个临界资源,它创建出来一般是一个信号量集的形式。

    这里要提到的信号量,也是一个计数器,用来标识资源的个数,我们知道完成线程之间的互斥可以使用互斥锁,其实mutex也可以认为是一个计数器标识资源的可用数量,只是它的值非0即1,加锁时表示要使用资源,将mutex减1,释放锁表示有资源可用,将mutex的值还原为1。

    而信号量和mutex类似,只是它标识资源的数量可以大于1,用来实现线程间的同步,即,如果信号量的值为1,则可以认为和互斥锁一样。下面要谈及的是POSIX semaphore库函数,这种信号量不仅可以用于同一进程的线程间同步,也可用于不同进程间同步。

-------------------------------------------------------------------------------------------


二. 信号量函数

  1. 信号量的创建及销毁

wKioL1ccG9PDucYtAAAjIsdGZmk716.png

函数参数中,

sem是一个指向sem_t也就是信号量类型的一个指针;

pshared表示该信号量是否用于不同进程间同步,为0表示用于同一进程的线程间同步;

value表示要将该信号量初始化的值;


wKiom1ccGw6yjDNBAAAJW6lCbWo933.png

函数参数中,sem表示要销毁哪一个信号量;


2. 信号量值的增加及减少

wKiom1ccHCiR98Q-AAAOBX3Gxu0763.png

对信号量进行wait操作其实就相当于进程间通信中对信号量集中某个信号量进行P操作,也就是要使用某个资源,就要将该信号量标识的资源数减1,如果该信号量值已经为0,则挂起等待;

函数参数中,sem表示要进行减法操作的信号量;

sem_trywait表示对该信号量进行操作的非阻塞版本;

sem_timedwait表示等待一定时间如果不能wait到该信号量就不再等待,timeout表示等待的时间;


wKioL1ccHO7y2Sb6AAAJhGb5rvE392.png

sem_post操作就是当使用完这个资源之后就要将其放回以供其他线程使用,因此进行加法操作,sem表示要进行操作的信号量,同时唤醒等待的线程;

-------------------------------------------------------------------------------------------

栗子时间:

1. 单生产者和单消费者模型

    以前谈论过的生产者与消费者模型的生产场所是链表,其实也可以使用一个环形缓冲区来模拟,环形缓冲区就是可以定义一个数组,当访问到数组最后一个位置的时候再从数组的第一个位置开始访问,以此循环。

    生产者不断地往环形缓冲区里生产数据,而消费者不断地从环形缓冲区中取出数据,但是,如果缓冲区已经全部占满,生产者就不能再生产了,同样,当缓冲区没有数据时,消费者也无法消费;可以看出,生产者需要的是缓冲区中剩余的空间个数,而消费者关心的是缓冲区中的数据个数;当生产者每产生一个数据的时候,缓冲区中空间的个数就要减少一个,数据的个数就会增加;而每当消费者取走一个数据的时候,缓冲区中数据的个数就会减少但是剩余空间数就会增加;

    而且,需要知道的是,在二者围绕着环形缓冲区操作的时候,消费者需要紧跟在生产者的后面,消费者不能超过生产者去读取数据,生产者也不能把消费者超过一圈,因为会将消费者还未读取的数据给覆盖;因此,就可以使用两个信号量来标识缓冲区中剩余空间个数和缓冲区中的数据个数;


wKioL1ccKjeSfPTtAACcmh-xx2M322.png

wKiom1ccKXLhOQXjAABQVeLg-2s739.png


运行程序,结果如下:

wKiom1ccJw_x4JmpAAAL7sWC8Og356.png


    以前谈论到的用条件变量来完成线程间的同步,一般都是要和互斥量来搭配使用,而这里所举的栗子,并没有用到互斥的量,这是因为,栗子中用信号量来标识两种不同的资源,这两种资源虽然是独立的,但同时又相互影响相互依赖,当生产者正在对一个空间放入数据的时候,也就是数据还没有放入正要放,此时如果消费者也要对这个空间进行操作是不成立的,因为这个时候数据的信号量为0,消费者线程需要挂起等待,因此并不会产生访问冲突,也就是说在上面这种情景下,生产者和消费者是没有互斥关系的。

-------------------------------------------------------------------------------------------

2. 多生产者和多消费者模型

    上面提到的单生产者和单消费者模型中,二者之间只用信号量来完成线程间的同步就可以了,并不需要互斥,但是,如果存在多个生产者和多个消费者呢?

    这里就需要用到互斥量了,因为生产者和生产者之间是要存在互斥关系的,当一个生产者向缓冲区中写入数据的时候,另一个生产者如果也像缓冲区中进行操作,就会覆盖掉上一个生产者所写入的数据;同样,当一个消费者从缓冲区中取出数据的时候,另一个消费者就不能取到数据了,因此,可以在上面的栗子中加入互斥锁:

wKiom1ccTO6DPzevAAAbevTZggw201.png

上面创建了两个生产者线程执行相同的生产者线程和两个消费者线程执行相同的消费者函数;


加锁可以加在不同的位置,但是锁加在不同的位置会产生不同的结果:

位置一:

wKioL1ccTPXjGJLAAACh7VYRi58707.png


上面这种位置就是将锁加在了信号量的外部,分析一下,当消费者先一步获得锁的时候,去缓冲区中取出数据执行sem_wait,这时候生产者没有获得锁也就是并没有向缓冲区中写入数据,那么因为信号量为0消费者就会挂起等待,但此时它占据着唯一一把互斥锁,因此这把锁就永远不会得到释放,也就产生了死锁:

wKioL1ccTPiQN85iAAAKFQG3Oxg363.png

但是,如果将消费者函数中获得锁pthread_mutex_lock函数放在sem_wait函数的后面,运行程序也会出现上面这种情况,但这并不是死锁,是因为生产者一直在不断地获得锁,而消费者一直没有得到锁资源也就不会打印出数据,如果在生产者函数里面加上一条打印语句就知道这并不是死锁了;


位置二:

wKioL1ccYirg7vv_AACw8C4002E439.png


上面程序中是将互斥锁加在了信号量的里面,运行分析程序:

wKiom1ccZRfz4U1uAADXdKydYkE755.png


-------------------------------------------------------------------------------------------

总结:

  1. 信号量可以用来进行一个进程的线程间同步,也可以用来不同进程间的同步,而信号量集是进程间通信的一种机制,相同点就是它们都用来标识临界资源的有无,进行加减操作;

  2. 和用条件变量来完成线程间同步需要和互斥量协作不同的是,在单消费者单生产者模型中用信号量来完成线程间同步并不需要互斥的关系,因为当信号量为0的时候就表示资源不可用需要挂起等待;

  3. 在多生产者和多消费者模型中就需要用到互斥量,但是和条件变量中pthread_cond_wait函数不同的是,sem_wait挂起等待的时候并不会释放锁资源,因为信号量并不依赖锁;

  4. 多生产者之间和多消费者之间竞争锁资源的时候,加锁的位置会对结果有很大影响,要注意避免死锁。



《完》

本文出自 “敲完代码好睡觉zzz” 博客,请务必保留此出处http://2627lounuo.blog.51cto.com/10696599/1767221

阅读全文
0 0