• 为了保证你在浏览本网站时有着更好的体验,建议使用类似Chrome、Firefox之类的浏览器~~
    • 如果你喜欢本站的内容何不Ctrl+D收藏一下呢,与大家一起分享各种编程知识~
    • 本网站研究机器学习、计算机视觉、模式识别~当然不局限于此,生命在于折腾,何不年轻时多折腾一下

从零学习多线程第四篇__信号量

Linux admin 5年前 (2015-05-30) 1408次浏览 0个评论 扫描二维码

信号量

信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即 资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保 持灯亮状态。当然,这样的操作原语也意味着更多的开销,而只有 0 和 1 两种取值的信号量叫做二进制信号量。

信号量相关函数

信号灯创建

int sem_init(sem_t *sem, int pshared, unsigned int value);

该函数初始化由 sem 指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared 控制信号量的类型,如果其值为 0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value 为 sem 的初始值。若 value 为 0 则是正常二进制信号。调用成功时返回 0,失败返回-1。

信号灯点亮

int sem_post(sem_t *sem);

点灯操作将信号灯值原子地加 1,表示增加一个可访问的资源。

信号等熄灭

int sem_wait(sem_t *sem);

该函数用于以原子操作的方式将信号量的值减 1。原子操作就是,如果两个线程企图同时给一个信号量加 1 或减 1,它们之间不会互相干扰

信号灯摧毁

int sem_destroy(sem_t *sem);

被注销的信号灯 sem 要求已没有线程在等待该信号灯,否则返回-1,且置 errno 为 EBUSY。除此之外,LinuxThreads 的信号灯注销函数不做其他动作。

获取灯值

int sem_getvalue(sem_t * sem, int * sval);

读取 sem 中的灯计数,存于*sval 中,并返回 0。

理解

sem_post 函数的作用是给信号量的值加上一个“1”,它是一个“原子操作”即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;而同时对同一个文件进行读、加和写操作的两个程序就有可能会引起冲突。信号量的值永远会正确地加一个“2”--因为有两个线程试图改变它。

sem_wait 函数也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为 2 的信号量调用 sem_wait(),线程将会继续执行,信号量的值将减到 1。如果对一个值为 0 的信号量调用 sem_wait(),这个函数就会地等待直到有其它线程增加了这个值使它不再是 0 为止。如果有两个线程都在 sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。

信号量这种“只用一个函数就能原子化地测试和设置”的能力下正是它的价值所在。还有另外一个信号量函数 sem_trywait,它是 sem_wait 的非阻塞搭档。

信号量的实例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> //包含线程相关头文件
#include <errno.h>
#include <sys/ipc.h>
#include <semaphore.h> //包含信号量相关头文件
int lock_var;
time_t end_time;
sem_t sem1,sem2; //声明两个信号量

void pthread1(void *arg); //声明两个线程函数
void pthread2(void *arg);

int main(int argc, char *argv[])
{
pthread_t id1,id2; //声明两个线程
pthread_t mon_th_id;
int ret;
end_time = time(NULL)+30;
ret=sem_init(&sem1,0,1); //对信号量进行初始化,第一个 0 表示此信号量子整个进程中共享,第二个 1 表示信号量初始值
ret=sem_init(&sem2,0,0);//对信号量的初始化,信号量的值为 0
if(ret!=0)
{
perror("sem_init");
}
ret=pthread_create(&id1,NULL,(void *)pthread1, NULL); //创建线程
if(ret!=0)
perror("pthread cread1");
ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
if(ret!=0)
perror("pthread cread2");
pthread_join(id1,NULL); //用来等待线程 1 的结束
pthread_join(id2,NULL); //用来等待线程 2 的结束
exit(0);
}

void pthread1(void *arg) //线程 1 的执行内容
{
int i;
while(time(NULL) < end_time){
sem_wait(&sem2); //线程阻塞一直等到 sem2 信号量大于 0,执行后将 sem2 减 1,代表资源已经被使用
for(i=0;i<2;i++){
sleep(1);
lock_var++;
printf("lock_var=%d\n",lock_var);
}
printf("pthread1:lock_var=%d\n",lock_var);
sem_post(&sem1); //将信号量 sem1 的值加 1,代表资源增加
sleep(1);
}
}

void pthread2(void *arg)
{
int nolock=0;
int ret;
while(time(NULL) < end_time){
sem_wait(&sem1);
printf("pthread2:pthread1 got lock;lock_var=%d\n",lock_var);
sem_post(&sem2);
sleep(3);
}
}

注释:主线成创建了两个子线程,线程函数分别为 pthread1 与 pthread2,pthread1 中的由于信号灯 sem2 的值为 0 所以此时该线程被阻塞,直到别的线程将此信号灯的值加 1 此线程才会被唤醒继续运行,因此 pthread2 率先执行,在此线程函数中一开始遇到 sem_wait,但是由于信号灯 sem1 初始值为 1 因此继续运行该线程函数,直到运行到 sem_post(&sem2)另外一个线程被唤醒继续运行,pthread2 休眠 3 秒。如此依次交替运行直到 30 秒结束。


Deeplearn, 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明从零学习多线程第四篇 __ 信号量
喜欢 (0)
admin
关于作者:
互联网行业码农一枚/业余铲屎官/数码影音爱好者/二次元

您必须 登录 才能发表评论!