Linux多线程--POSIX信号量、互斥锁、读写锁、条件变量

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Alatebloomer/article/details/81944626

Posix信号量

Posix 信号量

有名信号量

无名信号量

sem_open

sem_init

sem_close

sem_destroy

sem_unlink

                                                                                         sem_wait

                                                                                          sem_post

有名信号量

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
int sem_close(sem_t *sem);
int sem_unlink(const char *name);

与Posix类IPC用法类似: 名字以/somename形式标识,且只能有一个/ ,并且总长不能超过NAME_MAX-4 (i.e., 251)。

  Posix有名信号量需要用sem_open 函数创建或打开,PV操作分别是sem_wait 和 sem_post,可以使用sem_close 关闭,删除用sem_unlink。

  有名信号量用于不需要共享内存的进程间同步(可以通过名字访问), 类似System V 信号量。

无名信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);

无名信号量只存在于内存中, 并要求使用信号量的进程必须可以访问内存; 这意味着他们只能应用在同一进程中的线程, 或者不同进程中已经映射相同内存内容到它们的地址空间中的线程.

  无名信号量必须用sem_init 初始化,sem_init 函数的第二个参数pshared决定了线程共享(pshared=0)还是进程共享(pshared!=0),也可以用sem_post 和sem_wait 进行操作,在共享内存释放前,无名信号量要先用sem_destroy 销毁。

Posix信号量PV操作

int sem_wait(sem_t *sem);	//P操作
int sem_post(sem_t *sem);	//V操作

wait操作实现对信号量的减1, 如果信号量计数原先为0则会发生阻塞;

  post操作将信号量加1, 在调用sem_post时, 如果在调用sem_wait中发生了进程阻塞, 那么进程会被唤醒

Posix互斥锁

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,   const pthread_mutexattr_t *mutexattr);		
//互斥锁初始化, 注意:函数成功执行后,互斥锁被初始化为未锁住状态。

int pthread_mutex_lock(pthread_mutex_t *mutex);	    //互斥锁上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);	//非阻塞版本
int pthread_mutex_unlock(pthread_mutex_t *mutex);	//互斥锁解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);	//消除互斥锁

初始化锁用pthread_mutex_init,也可以用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER(普通锁,最常见)来初始化;销毁用pthread_mutex_destroy,Linux中互斥锁并不占用资源,所以不去销毁也可以。一旦互斥锁被锁住了(pthread_mutex_lock),另一个地方再调用pthread_mutex_lock,就会被阻塞住,直到有pthread_mutex_unlock来解锁这个互斥锁,以此来保证多线程执行的有序性。pthread_mutex_trylock不会被阻塞住,如果当前互斥锁被锁住了,pthread_mutex_trylock会返回一个异常值;如果没被锁住,就去锁定之,和pthread_mutex_lock效果一样

互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,也就是上锁/解锁,可以把互斥锁看作某种意义上的全局变量。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会阻塞,直到上锁的线程释放掉互斥锁为止。可以说,这把互斥锁保证让每个线程对共享资源按顺序进行原子操作。

  其中,互斥锁可以分为快速互斥锁(默认互斥锁)、递归互斥锁和检错互斥锁。这三种锁的区别主要在于其他未占有互斥锁的线程在希望得到互斥锁时是否需要阻塞等待。快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。递归互斥锁能够成功地返回,并且增加调用线程在互斥上加锁的次数,而检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。

三. 生产者消费者问题

#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
 
#include <semaphore.h>
 
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
 
#define ERR_EXIT(m) \
	do \
	{	\
		perror(m); \
		exit(EXIT_FAILURE); \
	}while(0)
	
#define CONSUMERS_COUNT 1   //生产者个数
#define PRODUCERS_COUNT 5   //消费者
#define BUFFSIZE 10  // 缓冲区大小
 
int g_buffer[BUFFSIZE]; /// 产品ID保存在缓冲区中
 
unsigned short in=0;  // 从in位置放产品
unsigned short out=0;  // 从out的位置消费产品
unsigned short produce_id =0;  // 产品ID
unsigned short consume_id =0;  // 消费产品ID
 
sem_t  g_sem_full;  //full信号量
sem_t  g_sem_empty;  //empty信号量
pthread_mutex_t   g_mutex;  // 互斥锁
 
 
// 创建的线程ID保存在g_thread中
pthread_t g_thread[CONSUMERS_COUNT+PRODUCERS_COUNT];
 
///消费者
void* consume(void* arg)
{
	int num = (int)arg;
	int i;
	while(1)
	{
		printf("%d wait buffer not empty\n",num);
		sem_wait(&g_sem_empty);
		pthread_mutex_lock(&g_mutex);
		
		//打印仓库状态
		for(i=0;i<BUFFSIZE;++i)
		{
			printf("%02d ",i);
			if(g_buffer[i] == -1)
				printf("%s","null");
			else
				printf("%d",g_buffer[i]);
			if(i == out)
				printf("\t<--consume");
			printf("\n");
		}
		// 消费产品
		consume_id = g_buffer[out];
		printf("%d begin consume product %d\n",num,consume_id);
		g_buffer[out] = -1;  // 取走产品,得把缓冲区设置为-1
		out = (out+1)%BUFFSIZE;
		printf("%d end consume product %d\n",num,consume_id);
		
		
		pthread_mutex_unlock(&g_mutex);
		sem_post(&g_sem_full);
		sleep(1);
	}
	return NULL;
}
 
//// 生产者
void* produce(void* arg)
{
	int i;
	int num = (int)arg;
	while(1)
	{
		printf("%d wait buffer not full\n",num);
		sem_wait(&g_sem_full);
		pthread_mutex_lock(&g_mutex);
		
		// 打印仓库的状态
		for(i=0;i<BUFFSIZE;++i)
		{
			printf("%02d ",i);
			if(g_buffer[i] == -1)
				printf(" %s","null");
			else
				printf("%d ",g_buffer[i]);
			if(i == in)
				printf("\t<--produce");
			printf("\n");
		}
		// 生产产品
		printf("%d begin produce produce %d \n",num,produce_id);
		g_buffer[in] = produce_id;
		in = (in+1)%BUFFSIZE;
		printf("%d end produce product %d\n",num,produce_id++);
		
		pthread_mutex_unlock(&g_mutex);
		sem_post(&g_sem_empty);
		
		sleep(5);
	}
	return NULL;
}
 
int main(void )
{
	int i=0;
	for(i=0;i<BUFFSIZE;++i)
		g_buffer[i] = -1;  // 初始化仓库都为-1
	// 初始化信号量
	sem_init(&g_sem_full,0,BUFFSIZE); //g_sem_full 信号的初始值为BUFFSIZE
	sem_init(&g_sem_empty,0,0);
	//初始化互斥锁
	pthread_mutex_init(&g_mutex,NULL);
	
	/// 创建消费者线程
	for(i=0;i<CONSUMERS_COUNT;++i)
		pthread_create(&g_thread[i],NULL,consume,(void*)i);
	/// 创建生产者线程
	for(i=0;i<PRODUCERS_COUNT;++i)
		pthread_create(&g_thread[CONSUMERS_COUNT+i],NULL,produce,(void*)i);
	// 等待线程的结束	
	for(i=0;i<CONSUMERS_COUNT+PRODUCERS_COUNT;++i)
		pthread_join(g_thread[i],NULL);
	
	return 0;
}
 

自旋锁与读写锁

自旋锁

int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
 
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
 
int pthread_spin_unlock(pthread_spinlock_t *lock);

自旋锁类似于互斥锁, 它的性能比互斥锁更高;

  自旋锁与互斥锁很重要的一个区别在于: 线程在申请自旋锁的时候, 线程并不会挂起, 它总是处于忙等待的状态(一直在自旋, CPU处于空耗的状态);

 自旋锁可用于以下情况:锁被持有的时间短, 而且线程并不希望在重新调度上花费太多的成本.

 

读写锁

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                        const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
 
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

只要没有线程持有给定的读写锁用于写,那么任意数目线程可以持有读写锁用于读.
仅当没有线程持有某个给定的读写锁用于读或者用于写时才能分配读写锁用于写.
读写锁用于读称为共享锁,读写锁用于写称为排他锁.

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

最为常见就是在线程池中,起初没有任务时任务队列为空,此时线程池中的线程因为“任务队列为空”这个条件处于阻塞状态。一旦有任务进来,就会以信号量的方式唤醒一个线程来处理这个任务。这个过程中就使用到了条件变量pthread_cond_t。

互斥锁用于上锁,条件变量则用于等待。这两种不同类型的同步都非常重要。

   条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。

//条件变量使用规范
//等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);


//给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
 
#include <semaphore.h>
 
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
 
#define ERR_EXIT(m) \
	do \
	{	\
		perror(m); \
		exit(EXIT_FAILURE); \
	}while(0)
	
#define CONSUMERS_COUNT 2
#define PRODUCERS_COUNT 4
 
pthread_cond_t g_cond;
pthread_mutex_t g_mutex;
 
// 创建的线程ID保存在g_thread中
pthread_t g_thread[CONSUMERS_COUNT+PRODUCERS_COUNT];
 
int nready=0;
 
///消费者
void* consume(void* arg)
{
	int num = (int)arg;
	int i;
	while(1)
	{
		pthread_mutex_lock(&g_mutex);
		while(nready == 0)
		{
			printf("(%d)begin wait a condition....\n",num);
			pthread_cond_wait(&g_cond,&g_mutex);
		}
		printf("(%d) end wait a condition....\n",num);
		printf("(%d) begin consume product ....\n",num);
		--nready;
		printf("(%d) end consume product ....\n",num);
		pthread_mutex_unlock(&g_mutex);
		sleep(1);
	}
	return NULL;
}
 
//// 生产者
void* produce(void* arg)
{
	int i;
	int num = (int)arg;
	while(1)
	{
		pthread_mutex_lock(&g_mutex);
		printf(" %d begin produce product ...\n",num);
		++nready;
		printf(" %d end produce product....\n",num);
		pthread_cond_signal(&g_cond);
		printf(" %d signal \n",num);
		pthread_mutex_unlock(&g_mutex);
		sleep(5);
	}
	return NULL;
}
 
int main(void )
{
	int i=0;
	
	//初始化互斥锁
	pthread_mutex_init(&g_mutex,NULL);
	//初始化条件变量
	pthread_cond_init(&g_cond,NULL);
	
	/// 创建消费者线程
	for(i=0;i<CONSUMERS_COUNT;++i)
		pthread_create(&g_thread[i],NULL,consume,(void*)i);
	sleep(1);
	/// 创建生产者线程
	for(i=0;i<PRODUCERS_COUNT;++i)
		pthread_create(&g_thread[CONSUMERS_COUNT+i],NULL,produce,(void*)i);
	// 等待线程的结束	
	for(i=0;i<CONSUMERS_COUNT+PRODUCERS_COUNT;++i)
		pthread_join(g_thread[i],NULL);
		
	//销毁互斥锁和条件变量	
	pthread_mutex_destroy(&g_mutex);
	pthread_cond_destroy(&g_cond);
	
	return 0;
}
 

分析:

//消费者
    while(1)
    {
        pthread_mutex_lock(&g_mutex);
        while(nready == 0)
        {
            pthread_cond_wait(&g_cond,&g_mutex);
        }
        --nready;
        pthread_mutex_unlock(&g_mutex);
    }

//生产者
    while(1)
    {
        pthread_mutex_lock(&g_mutex);
        ++nready;
        if(nready>0)
            pthread_cond_signal(&g_cond);
        pthread_mutex_unlock(&g_mutex);
    }


1. pthread_cond_wait(&g_cond,&g_mutex);所做的3件事
(1)对g_mutex进行解锁,(让别的线程改变条件变量,达到满足条件)
(2)等待条件,直到有线程想它发起通知
(3)重新对g_mutex进行加锁操作

2. 分析为什么使用while,而不用if语句
pthread_cond_signal 会向第一个等待的线程发起通知,如果没有任何一个线程处理等待状态,这个通知将被忽略
pthread_cond_broadcast 向所有等待线程发起通知

If  a  signal is delivered to a thread waiting for a condition variable,upon return from the signal handler the thread resumes waiting  for  the condition variable as if it was not interrupted, or it shall return zero due to spurious wakeup.(虚假唤醒)
如果是虚假唤醒,条件并没有改变,需要用while再次判断条件是否满足.

猜你喜欢

转载自blog.csdn.net/Alatebloomer/article/details/81944626