POSIX信号量互斥锁自旋锁读写锁

Posted 清水寺扫地僧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POSIX信号量互斥锁自旋锁读写锁相关的知识,希望对你有一定的参考价值。



1. 信号量和互斥锁

信号量 sem_t(使用sem_init()或是sem_open()),互斥锁 pthread_mutex(创建使用pthread_mutex_init())。

以生产者和消费者问题的实现来对POSIX信号量和互斥锁进行掌握,大体的程序思路如下。

两个信号量(sem_full,sem_empty)和一个互斥量(sem_mutex):

  • sem_full 表示缓冲区是否已满,对于生产者进行生产前需先P(sem_full),对于消费者而言进行消费后需要V(sem_full);
  • sem_empty 表示缓冲区是否为空,对于生产者生产后需V(sem_empty),对于消费者进行消费前需要P(sem_empty);
  • sem_mutex 对缓冲区进行加锁,防止竞争条件的发生,划分临界区,保护临界资源的安全共享访问;

程序思路如下图:
在这里插入图片描述
程序代码如下:

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <semaphore.h>

#define ERR_EXIT(m) 
	do {
		perror(m);
		exit(EXIT_FAILURE);
	} while(0)

#define CONSUMER_COUNT 1 //消费者个数
#define PRODUCER_COUNT 5 //生产者个数
#define BUFFERSIZE 10    //缓冲区大小

int g_buffer[BUFFSIZE]; //环形缓冲区,缓冲区大小为10
unsigned short in = 0;  //生产的产品下标序号
unsigned short out = 0; //消费的产品下标序号
unsigned short produce_id = 0; //生产的产品序号,即生产的第几个产品
unsigned short consume_id = 0; //消费的产品序号

sem_t g_sem_full; //缓冲区满信号量,为0表示已满
sem_t g_sem_empty; //缓冲区空信号量,为0表示全空
pthread_mutex_t g_mutex; //互斥锁,设置临界区

pthread_t g_thread[CONSUMER_COUNT + PRODUCER_COUNT]; //线程数组,存储生产者和消费者线程

void* consume(void *arg) {
	int num = (int)arg;
	int i;
	while(1) {
		printf("%d wait buffer not empty", num);
		sem_wait(&g_sem_empty);
		
		//临界区
		pthread_mutex_lock(&g_mutex);
		//打印缓冲区中的产品状况,即打印仓库当前的状态
		for(i = 0; i < BUFFERSIZE; i++) {
			printf("%02d ", i);
			if(-1 == g_buffer[i]) 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 %d\\n", num, consume_id);
		g_buffer[out] = -1;
		out = (out + 1) % BUFFERSIZE;
		printf("%d end consume %d\\n", num, consume_id);
		pthread_mutex_unlock(&g_mutex);
			
		sleep(5);
		sem_post(&g_sem_full);
	}
	return NULL;
}

void* produce(void *arg) {
	int num = (int)arg;
	int i;
	while(1) {
		printf("%d wait buffer not full", num);
		sem_wait(&g_sem_full);

		//临界区
		pthread_mutex_lock(&g_mutex);
		//打印缓冲区中的产品状况,即打印仓库当前的状态
		for(i = 0; i < BUFFERSIZE; i++) {
			printf("%02d ", i);
			if(-1 == g_buffer[i]) printf("%s", "null");
			else printf("%d", g_buffer[i]);

			if(i == in) printf("\\t<--produce");
			printf("\\n");
		}
		
		printf("%d begin produce %d\\n", num, produce_id);
		g_buffer[in] = product_id;
		in = (in + 1) % BUFFERSIZE;
		printf("%d end produce %d\\n", num, produce_id++);
		pthread_mutex_unlock(&g_mutex);
		
		sleep(1);
		sem_post(&g_sem_empty);
	}
	return NULL;
}

int main() {
	//将缓冲区中元素都初始化为-1,表示没有产品
	for(int i = 0; i < BUFFERSIZE; i++)
		g_buffer[i] = -1;
		
	//初始化匿名信号量,第二个参数pshared为空,表明只是一个进程中的线程进行协同或是互斥,
	//第三个参数表示给信号量的赋值
	sem_init(&g_sem_full, 0, BUFFERSIZE); 
	sem_init(&g_sem_empty, 0, 0);

	pthread_mutex_init(&g_mutex, NULL); //采用互斥锁的默认属性

	//创建若干个线程
	int i;
	for(int i = 0; i < CONSUMER_COUNT; i++) //创建若干个消费者
		pthread_create(&g_thread[i], NULL, consume, (void*)i);
		//对于这里的i,若是传入指针可能会有竞态问题,因为线程在进入入口函数是,i对应地址值的内容有可能已被
		//主线程所修改,即实际传入所创建线程的值是已经更改过的值,即出现竞态问题,所以还是使用值传递的方式为好;
		//这样采用值传递同样会造成兼容性问题,解决方法是使用一个malloc获取一个int*指针指向的堆内存
		//然后再将该int*内存指针作为参数传递进去,如下所示:
		/*int *p = malloc(sizeof(int));
		*p = i;
		pthread_create(&g_thread[i], NULL, consume, p);*/
	for(int i = 0; i < PRODUCER_COUNT; i++) //创建若干个生产者
		pthread_create(&g_thread[i], NULL, produce, (void*)i);

	for(int i = 0; i < PRODUCER_COUNT + CONSUME_COUNT; i++) //回收所有的线程
		pthread_join(g_thread[i], NULL);

	sem_destory(&g_sem_full); //销毁信号量
	sem_destory(&g_sem_empty);
	pthread_mutex_destory(&g_mutex); //销毁互斥量
	return 0;
}


2. 自旋锁

在这里插入图片描述



3. 读写锁

分为共享锁(读锁)和排他锁(写锁):
在这里插入图片描述

以上是关于POSIX信号量互斥锁自旋锁读写锁的主要内容,如果未能解决你的问题,请参考以下文章

互斥锁自旋锁读写锁和条件变量

详解linux多线程——互斥锁条件变量读写锁自旋锁信号量

互斥锁,自旋锁,原子操作原理和实现

五自旋锁(spinlock)

linux 自旋锁和信号量

转载同步和互斥的POSIX支持(互斥锁,条件变量,自旋锁)