简洁介绍信号量sem_t的起因、原理与使用

            简洁介绍信号量sem_t的起因、原理与使用

起因:

对应用程序优化加速时,会使用多线程技术来进行加速。使用软件多线程可以让用户与底层硬件进行隔离。即不管实际底层硬件是否有多个执行单元,都可以使用多线程来达到充分利用cpu资源,减少cpu等待延时的目的。

多线程在用户看来是并行执行的。在单执行单元的硬件平台上虽然在内核内多个线程之间是串行占用cpu资源并由cpu调度策略决定多个线程的执行顺序,但是从用户态看来多线程是并行执行的。而多执行单元的硬件平台上,多线程确实可以达到真实的并行执行。

同一个应用程序,不同线程上运行的代码不可能完全独立无关,即线程A上运行的代码codeA与线程B上运行的代码codeB总会有交互的需求。可以是执行顺序上的交互(执行前后关系),也可以是数据上的交互(访问修改相同的数据结构等)。

线程之间的交互需求可以通过多种技术来完成,信号量就是其中一种线程交互技术, 一般用来进行线程间的执行顺序上的交互。其他线程交互技术以后有空再谈。

原理:

信号量的发明非常简洁。可以将sem_t信号量类型看作是一个int类型typedef, 只不过程序员之间对这个变量类型具有约定的使用规则。全部相关函数只有下面几个,在头文件#include<semaphore.h>中进行声明。

声明一个信号量:sem_t  sem_var;

  1. int sem_init(sem_t *p_sem_var,int share_flag,unsigned int value);

  2. int sem_wait(sem_t *p_sem_var);

  3. int sem_trywait(sem_t *p_sem_var);

  4. int sem_post(sem_t *p_sem_var);

  5. int sem_destroy(sem_t *p_sem_var)用来释放信号量sem。 

1. sem_init():用于对指定信号初始化(*sem_var = value)。share_flag==0,表示信号在当前进程的多个线程之间共享;share_flag!=0,信号量在进程间共享。我们经常使用到的都是==0的情况。value表示初始化信号的值。 


 2.sem_wait():读取当前信号量的值sem_var并进行如下判断和操作:

                (1) if sem_var >0, then *p_sem_var--;并执行本线程sem_wait语句之后的代码。

                (2) if sem_var <=0, then  本线程执行暂停sem_wait后的代码,直到sem_var>0。一旦系统检测到sem_var重新>0则进行(1)的操作。

我们把线程因为某个信号量<=0的原因而停止执行后续代码的行为叫做线程阻塞

3.sem_trywait():读取当前信号量的值sem_var当大于0的时候p_sem_var--。<=0的时候不做操作。不管哪种情况,该线程都继续执行后续代码。可以看出sem_trywait是sem_wait的非阻塞版本哦。


4.sem_post():读取当前信号量的值sem_var并 *p_sem_var++。

试想一下,如果当前p_sem_var=0, 且已经有2个线程thread_a, thread_b阻塞在sem_var。那么某个线程thread_c调用sem_post函数的同时,与thread_c并行执行的thread_a, thread_b中的一个会因为检测到sem_var为1而停止阻塞。当有多个线程阻塞在这个信号量上时,调用一次sem_post只会使其中的一个线程不再阻塞。具体是哪个线程不再阻塞,则由线程的调度策略决定。 

5.sem_destroy() :销毁一个p_sem_var指向的信号量。仍有process或thread被阻塞(blocked)在这个sem_t的时候, 函数会返回错误码。

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

sem_wait(), sem_trywait(),sem_post()在计算机系统内部实现的时候都是一个“原子操作”。原子操作概念是否理解并不影响用户对信号量的使用。

原子操作,简单理解就是内核采用某种技术可以使得这个操作具有独立不可分割性。回到信号量就是对信号量的加减操作总是能够准确执行。多个线程同时调用原子操作的信号量操作函数的时候不会引起冲突而导致sem_var的值偏离用户的期望。即n个线程同时调用对同一个信号量做加“1”操作的函数后,sem_var确实就等于+=n的值,而不是出现+=n-1之类的错误结果值。

 

 使用举例:

有些人可能不太理解,计算机执行1+1难道会出现不等于2的结果吗?答案是确实。想象一下,如果线程A和线程B,都可以自由访问修改同一个地址存放的变量int i。int *p_i = &i。i此时等于0。然后在下一刻时间点如果两个线程都执行*p_i+=1的操作。那i会等于多少?我们知道+=操作对应到底层是一条计算机指令。为了执行这条执行需要进行取指令,译码,执行,存储等n个步骤。用户看起来并行执行的两个线程在深入到底层硬件的执行的某个最小不可分割的时刻并非并行,总是会存在先后顺序。对比下面2个线程A和线程B执行*p_i+=1的操作顺序的合理情形可以看出i的最终结果值是用户不可预测的。2个线程尚且如此,何况当存在16个或更多个线程的时候i的值就会千奇百怪啦:

(0)case0:

thread A读取*p_i处i的当前值,并放在寄存器中得到regA=0;

thread B读取*p_i处i的当前值,并放在寄存器中得到regB=0;

thread A给寄存器regA中的值执行+1的操作后regA等于1;

thread B给寄存器regB中的值执行+1的操作后regB等于1;

threadA将寄存器regA的值1写回到地址*p_i处, 使得i等于1;

threadA将寄存器regB的值1写回到地址*p_i处, 使得i等于1;

(1)case1:

thread A读取*p_i处i的当前值,并放在寄存器中得到regA=0;

thread A给寄存器regA中的值执行+1的操作后regA等于1;

threadA将寄存器regA的值1写回到地址*p_i处, 使得i等于1;

thread B读取*p_i处i的当前值,并放在寄存器中得到regB=1;

thread B给寄存器regB中的值执行+1的操作后regB等于2;

threadA将寄存器regB的值1写回到地址*p_i处, 使得i等于2;

所以, 可以看出为了正确的完成多个线程对相同共有变量的一系列操作行为。就必须明确规定其先后顺序。此时信号量就可以派上用场了。

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

如何修改上面的代码使得用户得到期望的i=2呢?

原始代码:

thread A:

{

*p_i+=1;

}

threadB:

{

*p_i+=1;

}

修改成下面的:

sem_t semA;//声明一个信号量

sem_init(&semA, 0, 0);//信号量初始化

thread A:

{

sem_wait();//等待信号量大于0

*p_i+=1;

}

thread B:

{

*p_i+=1;

sleep(5);

sem_post();//将信号量+1

}

【 转载、摘抄本文请 注明出处 并对本文 点赞,哈哈~~~。有需要请联系微信号81469486@qq.com】

 

  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值