信号量既可以作为二值计数器(即0,1),也可以作为资源计数器.
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数,它们都在头文件 /usr/include/semaphore.h中定义。
信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
而函数int sem_getvalue(sem_t *sem, int *sval);则用于获取信号量当前的计数. 函数sem_destroy(sem_t *sem)用来释放信号量sem。
可以用信号量模拟锁和条件变量:
- 锁,在同一个线程内同时对某个信号量先调用sem_wait再调用sem_post, 两个函数调用其中的区域就是所要保护的临界区代码了,这个时候其实信号量是作为二值计数器来使用的.不过在此之前要初始化该信号量计数为1,见下面例子中的代码.
- 条件变量,在某个线程中调用sem_wait, 而在另一个线程中调用sem_post.
不过, 信号量除了可以作为二值计数器用于模拟线程锁和条件变量之外, 还有比它们更加强大的功能, 信号量可以用做资源计数器, 也就是说初始化信号量的值为某个资源当前可用的数量, 使用了一个之后递减, 归还了一个之后递增。
信号量与线程锁,条件变量相比还有以下几点不同:
- 锁必须是同一个线程获取以及释放, 否则会死锁.而条件变量和信号量则不必.
- 信号的递增与减少会被系统自动记住, 系统内部有一个计数器实现信号量,不必担心会丢失, 而唤醒一个条件变量时,如果没有相应的线程在等待该条件变量, 这次唤醒将被丢失.
/* * ===================================================================================== * * Filename: pthread4.c * * Description: A program of Semaphore * * Version: 1.0 * Created: 03/13/2009 11:54:35 PM * Revision: none * Compiler: gcc * * Author: Futuredaemon (BUPT), gnuhpc@gmail.com * Company: BUPT_UNITED * * ===================================================================================== */ #include <stdio.h> #include <string.h> #include <pthread.h> #include <errno.h> #include <semaphore.h> #define BUFSIZE 4 #define NUMBER 8 int sum_of_number=0; /* 可读 和 可写资源数*/ sem_t write_res_number; sem_t read_res_number; /* 循环队列 */ struct recycle_buffer{ int buffer[BUFSIZE]; int head,tail; }re_buf; /* 用于实现临界区的互斥锁,我们对其初始化*/ pthread_mutex_t buffer_mutex=PTHREAD_MUTEX_INITIALIZER; static void *producer(void * arg) { int i; for(i=0;i<=NUMBER;i++) { /* 减少可写的资源数 */ sem_wait(&write_res_number); /* 进入互斥区 */ pthread_mutex_lock(&buffer_mutex); /*将数据复制到缓冲区的尾部*/ re_buf.buffer[re_buf.tail]=i; re_buf.tail=(re_buf.tail+1)%BUFSIZE; printf("procuder %d write %d./n",(int)pthread_self(),i); /*离开互斥区*/ pthread_mutex_unlock(&buffer_mutex); /*增加可读资源数*/ sem_post(&read_res_number); } /* 线程终止,如果有线程等待它们结束,则把NULL作为等待其结果的返回值*/ return NULL; } static void * consumer(void * arg) { int i,num; for(i=0;i<=NUMBER;i++) { /* 减少可读资源数 */ sem_wait(&read_res_number); /* 进入互斥区*/ pthread_mutex_lock(&buffer_mutex); /* 从缓冲区的头部获取数据*/ num = re_buf.buffer[re_buf.head]; re_buf.head = (re_buf.head+1)%BUFSIZE; printf("consumer %d read %d./n",pthread_self(),num); /* 离开互斥区*/ pthread_mutex_unlock(&buffer_mutex); sum_of_number+=num; /* 增加客写资源数*/ sem_post(&write_res_number); } /* 线程终止,如果有线程等待它们结束,则把NULL作为等待其结果的返回值*/ return NULL; } int main(int argc,char ** argv) { /* 用于保存线程的线程号 */ pthread_t p_tid; pthread_t c_tid; int i; re_buf.head=0; re_buf.tail=0; for(i=0;i<BUFSIZE;i++) re_buf.buffer[i] =0; /* 初始化可写资源数为循环队列的单元数 */ sem_init(&write_res_number,0,BUFSIZE); // 这里限定了可写的bufsize,当写线程写满buf时,会阻塞,等待读线程读取 /* 初始化可读资源数为0 */ sem_init(&read_res_number,0,0); /* 创建两个线程,线程函数分别是 producer 和 consumer */ /* 这两个线程将使用系统的缺省的线程设置,如线程的堆栈大小、线程调度策略和相应的优先级等等*/ pthread_create(&p_tid,NULL,producer,NULL); pthread_create(&c_tid,NULL,consumer,NULL); /*等待两个线程完成退出*/ pthread_join(p_tid,NULL); pthread_join(c_tid,NULL); printf("The sum of number is %d/n",sum_of_number); }