system v和posix版本信号量的接口函数


本篇介绍POSIX版本的Semaphore(信号量)


Mutex变量是非0即1的,可看作一种资源的可用数量,初始化时Mutex是1,表示有一个可用资源,加锁时获得该资源,将Mutex减到0,表示不再有可用资源,解锁时释放该资源,将Mutex重新加到1,表示又有了一个可用资源。


信号量(Semaphore)和Mutex类似,表示可用资源的数量,和Mutex不同的是这个数量可以大于1。如果信号量描述的资源数目是1时,此时的信号量和互斥锁相同!


POSIX semaphore库函数,这种信号量不仅可用于同一进程的线程间同步,也可用于不同进程间的同步。


有了互斥量和条件变量还提供信号量的原因是:信号量的主要目的是提供一种进程间同步的方式。这种同步的进程可以共享也可以不共享内存区。虽然信号量的意图在于进程间的同步,互斥量和条件变量的意图在于线程间同步,但信号量也可用于线程间同步,互斥量和条件变量也可通过共享内存区进行进程间同步。但应该根据具体应用考虑到效率和易用性进行具体的选择。



POSIX版本信号量分为有名信号量和无名信号量

有名信号量函数:

sem_open用于创建或打开一个信号量,信号量是通过name参数即信号量的名字来进行标识的。

sem_close用于关闭打开的信号量。当一个进程终止时,内核对其上仍然打开的所有有名信号量自动执行这个操作。

POSIX有名信号量是随内核持续的。

sem_unlink用于将有名信号量立刻从系统中删除,但信号量的销毁是在所有进程都关闭信号量的时候。

无名信号量有关函数:



以下代码是用无名信号量实现的生产者消费者模型


单生产者单消费者模型:

代码说明:

1.push()和pop()的数据(datatype)本篇用的是基本类型,如果是自定义类型的话,需要实现赋值运算符的重载

2.数组实际上是线性的,内存中并没有环形数组,我们定义了一个固定大小的数组,当数组的最后一个元素也被填上数据时,检查数组的第一个元素(下标为0的元素)是否已经被消费者读过,如果已经读过,那么生产者就可以继续放数据,当数组满时(即数组中的元素一个也没有被消费者读),生产者会等待。

3.sem_init()初始化两个信号量sem_p(控制生产者)和sem_c(控制消费者),pshared为0时,表示信号量用于同一进程的线程间同步

sem_destroy()使两个信号量回到初始化前的状态

sem_wait() 可以获得资源,相当于P操作,把给定信号量减一

sem_post() 可以释放资源,相当于V操作,进行加一操作

当信号量的值为0时,sem_wait()会将进程挂起等待,sem_trywait()不会将进程挂起

4.多生产者和多消费者模型则需要加锁,其实加用两把不同的锁实现效率更高,可以使线程并发执行,而我在下面代码中指使用了一把锁,在下面代码中我把它标记了出来


代码实现:

ring.cpp

#include<iostream>#include<stdlib.h>#include<semaphore.h>#include<pthread.h>usingnamespacestd;#defineSEM_PRO20#defineSEM_CON0#defineSIZESEM_PROtypedefintdatatype;datatypering[SIZE];//数组的定义datatypepro,con;sem_tsem_p;//productsem_tsem_c;//consumervoidinit_ring(datatype(*ring)[SIZE]){pro=0;con=0;}datatype&push(datatype&data,intindex){ring[pro++]=data;datatypetmp=ring[pro-1];pro%=SIZE;returntmp;}datatype&pop(intindex){con++;datatypetmp=ring[con-1];con%=SIZE;returntmp;}void*product(void*arg){while(1){datatypedata=rand()%50;sem_wait(&sem_p);datatypeval=push(data,pro);cout<<"productdone...,valis:"<<val<<endl;sem_post(&sem_c);sleep(1);}}void*consumer(void*arg){while(1){sem_wait(&sem_c);datatypeval=pop(con);cout<<"consumerdone...,valis:"<<val<<endl;sem_post(&sem_p);sleep(8);}}intmain(){init_ring(&ring);sem_init(&sem_p,0,SEM_PRO);sem_init(&sem_c,0,0);pthread_ttid1,tid2;pthread_create(&tid1,NULL,product,NULL);pthread_create(&tid2,NULL,consumer,NULL);sem_destroy(&sem_p);sem_destroy(&sem_c);pthread_join(tid1,NULL);pthread_join(tid2,NULL);return0;}

Makefile

ring:ring.cppg++-o$@$^-lpthread.PHONY:cleanclean:rm-fring


下面两次运行生产者和消费者的速度有所变化,导致运行结果不同

第一次运行结果:

第二次运行结果:


多生产者多消费者模型:

#include<iostream>#include<stdlib.h>#include<semaphore.h>#include<pthread.h>usingnamespacestd;#defineSEM_PRO20#defineSEM_CON0#defineSIZESEM_PROtypedefintdatatype;datatypering[SIZE];datatypepro,con;sem_tsem_p;//productsem_tsem_c;//consumervoidinit_ring(datatype(*ring)[SIZE]){pro=0;con=0;}datatype&push(datatype&data,intindex){ring[pro++]=data;datatypetmp=ring[pro-1];pro%=SIZE;returntmp;}datatype&pop(intindex){con++;datatypetmp=ring[con-1];con%=SIZE;returntmp;}void*product(void*arg){while(1){intvalue;datatypedata=rand()%50;sem_wait(&sem_p);//申请资源,进行减一操作pthread_mutex_lock(&lock);datatypeval=push(data,pro);//往buf里push数据sem_getvalue(&sem_p,&value);//得到此时信号量sem_p的值,即还有几个空格可>以使用pthread_mutex_unlock(&lock);cout<<"product"<<(int)arg<<"done...,valis:"<<val<<endl;cout<<"sem_pis:"<<value<<",";pthread_mutex_lock(&lock);//**********************************应加不同的锁sem_post(&sem_c);//释放资源sem_getvalue(&sem_c,&value);//得到此时信号量sem_c的值,即有多少数据可以>取pthread_mutex_unlock(&lock);cout<<"sem_cis:"<<value<<endl;sleep(5);}}void*consumer(void*arg){while(1){intvalue;sem_wait(&sem_c);pthread_mutex_lock(&lock);datatypeval=pop(con);sem_getvalue(&sem_c,&value);pthread_mutex_unlock(&lock);cout<<"consumer"<<(int)arg<<"done...,valis:"<<val<<endl;cout<<"sem_cis:"<<value<<",";pthread_mutex_lock(&lock);//**********************************应加不同的锁sem_post(&sem_p);sem_getvalue(&sem_p,&value);pthread_mutex_unlock(&lock);cout<<"sem_pis:"<<value<<endl;sleep(1);}}intmain(){init_ring(&ring);sem_init(&sem_p,0,SEM_PRO);sem_init(&sem_c,0,0);pthread_ttid1,tid2,tid3;pthread_create(&tid1,NULL,product,(void*)1);pthread_create(&tid2,NULL,product,(void*)2);pthread_create(&tid3,NULL,product,(void*)3);pthread_ttid4,tid5;pthread_create(&tid4,NULL,consumer,(void*)4);pthread_create(&tid5,NULL,consumer,(void*)5);sem_destroy(&sem_p);sem_destroy(&sem_c);pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_join(tid3,NULL);pthread_join(tid4,NULL);pthread_join(tid5,NULL);return0;}

运行结果:


很多时候信号量和互斥量,条件变量三者都可以再某种应用中使用,那这三者的差异有哪些呢,下面列出了这三者之间的差异:

互斥量必须由给它上锁的线程解锁。而信号量不需要由等待它的线程进行挂出,可以在其他进程进行挂出操作。

互斥量要么被锁住,要么是解开状态,只有这两种状态。而信号量的值可以支持多个进程成功进行wait操作。

信号量的挂出操作总是被记住,因为信号量有一个计数值,挂出操作总会将该计数值加1,然而当向条件变量发送一个信号时,如果没有线程等待在条件变量,那么该信号会丢失。