信号量(semaphore)是一种用于提供不同进程之间或者一个给定的不同线程间同步手段的原语。信号量多用于进程间的同步与互斥,简单的说一下同步和互斥的意思:
同步:处理竞争就是同步,安排进程执行的先后顺序就是同步,每个进程都有一定的先后执行顺序。
互斥:互斥访问不可共享的临界资源,同时会引发两个新的控制问题(互斥可以说是特殊的同步)。
竞争:当并发进程竞争使用同一个资源的时候,我们就称为竞争进程。
共享资源通常分为两类:一类是
互斥共享资源,即任一时刻只允许一个进程访问该资源;另一类是
同步共享资源,即同一时刻允许多个进程访问该资源;信号量是解决互斥共享资源的同步问题而引入的机制。
1.概述:
简单说一下信号量的工作机制(因为真的很简单),可以直接理解成计数器(当然其实加锁的时候肯定不能这么简单,不只只是信号量了),信号量会有初值(>0),每当有进程申请使用信号量,通过一个P操作来对信号量进行-1操作,当计数器减到0的时候就说明没有资源了,其他进程要想访问就必须等待(具体怎么等还有说法,比如忙等待或者睡眠),当该进程执行完这段工作(我们称之为临界区)之后,就会执行V操作来对信号量进行+1操作。
临界区:临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。
临界资源:只能被一个进程同时使用(不可以多个进程共享),要用到互斥。
我们可以说信号量也是进程间通信的一种方式,比如互斥锁的简单实现就是信号量,一个进程使用互斥锁,并通知(通信)其他想要该互斥锁的进程,阻止他们的访问和使用。
当有进程要求使用共享资源时,需要执行以下操作:
1.系统首先要检测该资源的信号量;
2.若该资源的信号量值大于0,则进程可以使用该资源,此时,进程将该资源的信号量值减1;
3.若该资源的信号量值为0,则进程进入休眠状态,直到信号量值大于0时进程被唤醒,访问该资源;
当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。
2.信号量的具体结构
每个信号量集都有一个与其相对应的结构,该结构定义如下:
/* Data structure describing a set of semaphores. */ struct semid_ds { struct ipc_perm sem_perm; /* operation permission struct */ struct sem *sem_base; /* ptr to array of semaphores in set */ unsigned short sem_nsems; /* # of semaphores in set */ time_t sem_otime; /* last-semop() time */ time_t sem_ctime; /* last-change time */ }; /* Data structure describing each of semaphores. */ struct sem { unsigned short semval; /* semaphore value, always >= 0 */ pid_t sempid; /* pid for last successful semop(), SETVAL, SETALL */ unsigned short semncnt; /* # processes awaiting semval > curval */ unsigned short semzcnt; /* # processes awaiting semval == 0 */ };
信号量集的结构图如下所示:
3.代码实现信号量
使用信号量实现生产者消费者模式
#include "stdafx.h" #include <stdio.h> #include <pthread.h> #include <sched.h> #include <semaphore.h> #include <conio.h> #include <ctype.h> #include <signal.h> #include <iostream> #include<Windows.h> using namespace std; #pragma comment(lib,"pthreadVC2.lib") #define N 5 //消费者或者生产者的数目 #define M 10 //缓冲数目 int productin = 0; //生产者放置产品的位置 int prochaseout = 0; //消费者取产品的位置 int buff[M] = {0}; //缓冲区初始化为0,开始时没有产品。 sem_t empty_sem; // 同步信号量,当满的时候阻止生产者放产品。 sem_t full_sem; //同步信号量,当没有产品的时候阻止消费者消费。 pthread_mutex_t mutex; //互斥信号量,一次只有一个线程访问缓冲区。 int product_id = 0; //生产者id int prochase_id = 0; //消费者id void SignalExit(int signo) { printf("程序退出%d\n",signo); return; } void PrintProduction() { printf("此时的产品队列为::"); for(int i = 0; i < M; i++ ) { printf("%d ",buff[i]); } printf("\n\n"); } //////////////////////生产者方法//////////////////// void* Product(void* pramter) { int id = ++product_id; while(1) { Sleep(5000); //毫秒 sem_wait(&empty_sem); //给信号量减1操作 pthread_mutex_lock(&mutex); productin = productin % M; printf("生产者%d在产品队列中放入第%d个产品\n\n",id,productin+1); buff[productin] = 1; PrintProduction(); ++productin; pthread_mutex_unlock(&mutex); //释放互斥量对象 sem_post(&full_sem); //给信号量的值加1操作 } } //////////////消费者方法/////////////////////// void* Prochase( void* pramter ) { int id = ++prochase_id; while(1) { Sleep(7000); sem_wait(&full_sem); pthread_mutex_lock(&mutex); prochaseout = prochaseout % M; printf("消费者%d从产品队列中取出第%d个产品\n\n",id,prochaseout+1); buff[prochaseout] = 0; PrintProduction(); ++prochaseout; pthread_mutex_unlock(&mutex); sem_post(&empty_sem); } } int main() { cout << "生产者和消费者数目都为5,产品缓冲区为10,生产者每2秒生产一个产品,消费者每5秒消费一个产品" << endl << endl; pthread_t productid[N]; pthread_t prochaseid[N]; int ret[N]; //初始化信号量 int seminit1 = sem_init(&empty_sem,0,M); int seminit2 = sem_init(&full_sem,0,0); if( seminit1 != 0 && seminit2 != 0 ) { printf("sem_init failed !!!\n"); return 0; } //初始化互斥信号量 int mutexinit = pthread_mutex_init(&mutex,NULL); if( mutexinit != 0 ) { printf("pthread_mutex_init failed !!\n"); return 0; } //创建n个生产者线程 for(int i = 0; i < N; i++ ) { ret[i] = pthread_create( &productid[i], NULL,Product,(void*)(&i) ); if( ret[i] != 0 ) { printf("生产者%d线程创建失败!\n",i); return 0; } } //创建n个消费者线程 for(int j = 0; j < N; j++ ) { ret[j] = pthread_create(&prochaseid[j],NULL,Prochase,NULL); if( ret[j] != 0 ) { printf("消费者%d线程创建失败\n",j); return 0; } } ///////////////////////等待线程被销毁/////////////////////////////////////////////// for( int k = 0; k < N; k++ ) { printf("销毁线程\n"); pthread_join(productid[k],NULL); pthread_join(prochaseid[k],NULL); } return 0; }