线程同步之信号量Semaphore

信号量是内核对象,它允许多个线程在同一个时刻访问同一个共享资源,但是需要限制在同一时刻访问此共享资源的最大线程数量。在创建信号量时,要指定允许的最大资源计数和当前可用的资源数。一般将当前可用资源数设置为最大资源数,每增加一个线程对共享资源的访问,当前可用资源数减1。当可用资源计数减小到0时,则说明当前占用资源的线程数达到了所允许的最大数目,其他线程无法再进入,必须等待(阻塞)。占用资源的线程在处理完成后,会释放占用的资源,此时可用资源计数加1,这时等待的线程队列中,会有其中一个线程被唤醒并获取资源。

信号量有两种类型:二进制信号量计数信号量
二进制信号量,只有0和1两种取值。一般用于保护一段代码使其每次只被一个线程执行,这种信号量可代替互斥锁来实现对资源的互斥访问。
计数信号量,可以有更大的取值范围,允许有限数目的线程共同访问某个共享资源。

常用的信号量操作有如下一些函数:

信号量的创建

 #include <semaphore.h>

 int sem_init(sem_t *sem, int pshared, unsigned int value);

 sem即要被创建并初始化的信号量指针;
 value用来指定信号量的初始值,也就是共享资源同时可被访问的最大线程数;

第二个参数pshared需要注意,该参数设置为0时,表明该信号量是在进程内使用,即实现线程之间的同步,pshared需要定义在所有线程都可见的位置,比如定义成全局变量,或者动态分配在堆上的变量。该参数设置为非零值时,用于进程之间的同步,这时信号量需要定义在共享内存区域中,任何可以访问该共享内存区域的进程都可以通过sem_post()和sem_wait()操作该信号量。

本篇我们只讨论进程内的同步。

信号量的控制

#include <semaphore.h>

int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

sem_post()会使信号量的值递增,如果信号量的值大于0,那么另一个阻塞在sem_wait()上等待该信号的线程就会被唤醒,并获取共享资源;

sem_wait()会使信号量的值递减。如果信号量的值大于0,那么执行递减操作并立即返回;若信号量的当前值为0,那么该调用将会被阻塞,直到信号量的值变为正值,或者被某个信号处理函数中断该调用。

sem_trywait(),与sem_wait()作用类似,不同的是当信号量的值为0而无法执行递减操作时,返回一个错误码(EAGAIN),而不是阻塞宿主线程的执行。

sem_timedwait(),与sem_wait()作用类似,不同的是设置了一个阻塞的时长abs_timeout,当阻塞时间超过该时长时,返回一个ETIMEDOUT错误。

信号量的销毁

#include <semaphore.h>

int sem_destroy(sem_t *sem);

sem_destroy()用来销毁一个未命名的信号量,信号量地址由参数sem指定。只有由sem_init()函数初始化的信号量才能调用sem_destroy()销毁。信号量在销毁前要确保没有其他线程或进程在占用它。一个信号量被销毁后不可再被使用,否则会产生未知结果,被销毁的信号量,可通过sem_init()重新初始化后再被使用。

下面我们通过一个例子来理解信号量的工作机制。

假设一个医院的某科室有三个医生,每个医生每次接诊一个病人,那么该科室最多可接诊的病人数量为3。假如一段时间内,该科室仅提供20个挂号名额,也就是说有20人就诊,那么我们如何使用信号量的机制来模拟该科室的就诊情况呢?

/* 医生和病人的例子:3个医生,20个病人,每次每个医生服务一个病人,也就是说,最多有3个病人可同时就诊 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>

/* 信号量定义为全局变量 */
sem_t g_sem;

/* 定义一定时间段内,接诊的病人总数量 */
const int PATIENT_NUM = 20;

/* 每个线程模拟一个病人的就诊 */
void *patient_service(void *arg)
{
	int patient_id = *((int *)arg);  //输入参数为病患ID
	
	if(sem_wait(&g_sem) == 0)   //病人等待服务资源,即医生空闲
	{
		printf("Patient %d is seeing the doctor. \n", patient_id);
		sleep(2);   //每个病人的就诊时间为2秒
		sem_post(&g_sem);   //当前病人服务完成,释放一个医生资源
	}
	
	return NULL;
}


int main()
{
	/* 初始化信号量,指定为线程之间的共享,信号量值为3 */
	sem_init(&g_sem, 0, 3);
	
	pthread_t patient_thread[PATIENT_NUM];   //每个病人为一个线程
	
	for(int i = 0; i < PATIENT_NUM; i++)
	{
		int ret;
		int patientID = i;
		
		/* 为第i个病人创建服务线程 */
		ret = pthread_create(&patient_thread[i], NULL, patient_service, &patientID);
		if(ret != 0)
		{
			printf("pthread_create error! \n");
			exit(EXIT_FAILURE);
		}
		usleep(50);
		
	}
	
	/* 连接已终止的线程 */
	for(int j = 0; j < PATIENT_NUM; j++)
	{
		pthread_join(patient_thread[j], NULL);
	}
	
	/* 最后释放信号量 */
	sem_destroy(&g_sem);
	
	return 0;
	
}

执行结果:

发布了57 篇原创文章 · 获赞 58 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/DeliaPu/article/details/96309521