在windows中使用信号量已经在另一篇文章中讲过了,信号量的详细细节也已经展示了,本文介绍如何在linux环境下使用c语言编写信号量类型的例子代码。
windows c语言使用信号量
与windows环境下不同,在linux下,头文件unistd.h或者pthread.h都没有直接包含P,V操作,也就是wait(),signal(),也即信号量的P,V操作需要自行编程实现。信号量结构体(参考上面链接)已经清楚,但是这还不够,实现信号量需要满足以下两个条件:
1.信号量操作只能是原子操作
2.除了P,V操作外,其他函数无法操作信号量
我们先看Linux下C语言提供的相关函数:
1.pthread_t;
2.pthread_mutex_t;
3.pthread_cond_t;
4.pthread_mutex_lock();
5.pthread_mutex_unlock();
6.pthread_cond_wait();
7.pthread_cond_signal();
8.pthread_mutex_init();
9.pthread_cond_init();
1-3为相关变量定义,4、5为互斥访问的锁操作,6、7为条件变量,8、9为初始化函数。
函数具体用法可以使用命令查看:
man pthread_cond_wait
#或者
man function
锁操作可以实现互斥访问,条件变量可以实现进程同步功能,因此这些函数可以组成信号量操作,函数具体内容就不再赘述。
首先定义信号量结构体:
typedef struct
{
int value;
pthread_mutex_t mutex;//信号量为原子操作
pthread_cond_t cond;
}sema_t;
value表示资源量,mutex实现原子操作,cond实现阻塞等待以及唤醒。
P操作即首先占用一个资源量,如果不够则阻塞,资源够用立即返回:
void sema_wait(sema_t *sema)
{
pthread_mutex_lock(&sema->mutex);
sema->value--;
while(sema->value<0)
pthread_cond_wait(&sema->cond,&sema->mutex);
pthread_mutex_unlock(&sema->mutex);
}
V操作首先释放一个资源量,并调唤醒一个条件变量,这里不用判断有没有被阻塞的进程,条件变量cond已经帮你实现了:
void sema_signal(sema_t *sema)
{
pthread_mutex_lock(&sema->mutex);
sema->value++;
pthread_cond_signal(&sema->cond);
pthread_mutex_unlock(&sema->mutex);
}
这样就完整实现了P,V操作。
下面看一个使用信号量的例子:
- 系统中有3个线程:生产者、计算者、消费者
- 系统中有2个容量为4的缓冲区:buffer1、buffer2
- 生产者
- 生产’a’、‘b’、‘c’、‘d’、‘e’、‘f’、‘g’、'h’八个字符
- 放入到buffer1
- 打印生产的字符
- 计算者
- 从buffer1取出字符
- 将小写字符转换为大写字符,按照 input:OUTPUT 的格式打印
- 放入到buffer2
- 消费者
- 从buffer2取出字符
- 打印取出的字符
- 程序输出结果(实际输出结果是交织的)
很明显,需要五个信号量:
sema_t mutex1,mutex2;//互斥访问缓冲区
sema_t wait_empty_buff1;
sema_t wait_full_buff1;
sema_t wait_empty_buff2;
sema_t wait_full_buff2;
produce线程负责向buffer1中写数据,有几个要求:
1.buff1互斥访问
2.缓冲区满不能写
3.写完通知缓冲区满
void *produce()
{
int item;
for(int i=0;i<item_count;i++)
{
sema_wait (&wait_empty_buff1);
sema_wait (&mutex1);
item='a'+i;
put_item(1,item);
printf("Produce item: %c\n",item);
sema_signal (&mutex1);
sema_signal (&wait_full_buff1);
}
return NULL;
}
consume线程与produce类似,也有如上述三个要求,但是是2、3是相反的并且互斥访问buff2:
1.buff2互斥访问
2.缓冲区空不能拿
3.拿完通知缓冲区空
void *consume()
{
int item;
for(int i=0;i<item_count;i++)
{
sema_wait (&wait_full_buff2);
sema_wait (&mutex2);
item=get_item(2);
printf("Consume item: %c\n",item);
sema_signal (&mutex2);
sema_signal (&wait_empty_buff2);
}
return NULL;
}
compute比较复杂,是上述两个线程的结合体,代码如下:
void *compute()
{
int item,item_res;
for(int i=0;i<item_count;i++)
{
sema_wait (&wait_full_buff1);
sema_wait (&mutex1);
item=get_item(1);
sema_signal (&mutex1);
sema_signal (&wait_empty_buff1);
item_res=item-('a'-'A');
printf("Compute item: %c-->%c\n",item,item_res);
sema_wait (&wait_empty_buff2);
sema_wait (&mutex2);
put_item(2,item_res);
sema_signal (&mutex2);
sema_signal (&wait_full_buff2);
}
return NULL;
}
由buff1拿出计算后放入buff2,拿的时候buff1不能空,放的时候buff2不能满。
信号量初始化:
mutex1.value=1;//互斥访问,一次只能允许一个
mutex2.value=1;
wait_empty_buff1.value=bsize-1;//缓冲区大小-1
wait_empty_buff2.value=bsize-1;
wait_full_buff1.value=0;//没有写数据时为空
wait_full_buff2.value=0;
至此全部完毕。
题目2:
系统中有2个线程:ping 线程和 pong 线程
- ping 线程先执行
- ping 线程执行流程如下
- 打印输出 ping
- 等待 pong 线程输出
- 执行第 1 步
- pong 线程执行流程如下
- 打印输出 pong
- 等待 ping 线程输出
- 执行第 1 步
- 程序输出结果
ping
pong
ping
pong
…
想法类似,读者可以自行实现
全部完整代码地址