C语言使用信号量(Linux)

2023-05-16

在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 线程执行流程如下
    1. 打印输出 ping
    2. 等待 pong 线程输出
    3. 执行第 1 步
  • pong 线程执行流程如下
    1. 打印输出 pong
    2. 等待 ping 线程输出
    3. 执行第 1 步
  • 程序输出结果
    ping
    pong
    ping
    pong

    想法类似,读者可以自行实现
    全部完整代码地址
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言使用信号量(Linux) 的相关文章

随机推荐