共计 2636 个字符,预计需要花费 7 分钟才能阅读完成。
信号量
信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即 资源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保 持灯亮状态。当然,这样的操作原语也意味着更多的开销,而只有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秒结束。