Linux多线程同步之读写锁

读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程。当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步,

和互斥量不同的是:互斥量会把试图进入已保护的临界区的线程都阻塞;然而读写锁会视当前进入临界区的线程和请求进入临界区的线程的属性来判断是否允许线程进入。

相对互斥量只有加锁和不加锁两种状态,读写锁有三种状态:读模式下的加锁,写模式下的加锁,不加锁

读写锁的使用规则:

  • 只要没有写模式下的加锁,任意线程都可以进行读模式下的加锁;
  • 只有读写锁处于不加锁状态时,才能进行写模式下的加锁;

读写锁也称为共享-独占(shared-exclusive)锁,当读写锁以读模式加锁时,它是以共享模式锁住,当以写模式加锁时,它是以独占模式锁住。读写锁非常适合读数据的频率远大于写数据的频率从的应用中。这样可以在任何时刻运行多个读线程并发的执行,给程序带来了更高的并发度。

需要提到的是:读写锁到目前为止仍然不是属于POSIX标准,本文讨论的读写锁函数都是有Open Group定义的的。例如下面是在我机器上,编译器是gcc version 4.4.6,关于读写锁的定义是包含在预处理命令中的:

[cpp]  view plain  copy
 print ?
  1. #if defined __USE_UNIX98 || defined __USE_XOPEN2K  
  2.   
  3. ... 读写锁相关函数声明...  
  4.   
  5. #endif  

1读写锁的初始化和销毁

[cpp]  view plain  copy
 print ?
  1. /* Initialize read-write lock  */  
  2.  int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock,  
  3.                                 __const pthread_rwlockattr_t *__restrict __attr);  
  4.   
  5. /* Destroy read-write lock */  
  6. extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock);  
  7.   
  8.                                                返回值:成功返回0,否则返回错误代码  

上面两个函数分别由于读写锁的初始化和销毁。和互斥量,条件变量一样,如果读写锁是静态分配的,可以通过常量进行初始化,如下:

[cpp]  view plain  copy
 print ?
  1. pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;  

也可以通过pthread_rwlock_init()进行初始化。对于动态分配的读写锁由于不能直接赋值进行初始化,只能通过这种方式进行初始化。pthread_rwlock_init()第二个参数是读写锁的属性,如果采用默认属性,可以传入空指针NULL

那么当不在需要使用时及释放(自动或者手动)读写锁占用的内存之前,需要调用pthread_rwlock_destroy()进行销毁读写锁占用的资源。

2读写锁的属性设置

[cpp]  view plain  copy
 print ?
  1. /* 初始化读写锁属性对象 */  
  2. int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr);  
  3.   
  4. /* 销毁读写锁属性对象 */  
  5. int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr);  
  6.   
  7. /* 获取读写锁属性对象在进程间共享与否的标识*/  
  8. int pthread_rwlockattr_getpshared (__const pthread_rwlockattr_t * __restrict __attr,  
  9.                                           int *__restrict __pshared);  
  10.   
  11. /* 设置读写锁属性对象,标识在进程间共享与否  */  
  12. int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *__attr, int __pshared);  
  13.   
  14.                                                     返回值:成功返回0,否则返回错误代码  
这个属性设置和互斥量的基本一样,具体可以参考互斥量的设


3读写锁的使用

[cpp]  view plain  copy
 print ?
  1. /* 读模式下加锁  */  
  2. int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock);  
  3.   
  4. /* 非阻塞的读模式下加锁  */  
  5. int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock);  
  6.   
  7. # ifdef __USE_XOPEN2K  
  8. /*  限时等待的读模式加锁 */  
  9. int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock,  
  10.                                        __const struct timespec *__restrict __abstime);  
  11. # endif  
  12.   
  13. /* 写模式下加锁  */  
  14. int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock);  
  15.   
  16. /* 非阻塞的写模式下加锁 */  
  17. int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock);  
  18.   
  19. # ifdef __USE_XOPEN2K  
  20. /* 限时等待的写模式加锁 */  
  21. int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock,  
  22.                                        __const struct timespec *__restrict __abstime);  
  23. # endif  
  24.   
  25. /* 解锁 */  
  26. int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock);  
  27.   
  28.                                                    返回值:成功返回0,否则返回错误代码  

1pthread_rwlock_rdlock()系列函数

pthread_rwlock_rdlock()用于以读模式即共享模式获取读写锁,如果读写锁已经被某个线程以写模式占用,那么调用线程就被阻塞。在实现读写锁的时候可以对共享模式下锁的数量进行限制(目前不知如何限制)。

pthread_rwlock_tryrdlock()pthread_rwlock_rdlock()的唯一区别就是,在无法获取读写锁的时候,调用线程不会阻塞,会立即返回,并返回错误代码EBUSY

pthread_rwlock_timedrdlock()是限时等待读模式加锁,时间参数struct timespec * __restrict __abstime也是绝对时间,和条件变量的pthread_cond_timedwait()使用基本一致,具体可以参考pthread_cond_timedwait() 

2pthread_rwlock_wrlock()系列函数

pthread_rwlock_wrlock()用于写模式即独占模式获取读写锁,如果读写锁已经被其他线程占用,不论是以共享模式还是独占模式占用,调用线程都会进入阻塞状态。

pthread_rwlock_trywrlock()在无法获取读写锁的时候,调用线程不会进入睡眠,会立即返回,并返回错误代码EBUSY

pthread_rwlock_timedwrlock()是限时等待写模式加锁,也和条件变量的pthread_cond_timedwait()使用基本一致,具体可以参考pthread_cond_timedwait()

3pthread_rwlock_unlock()

无论以共享模式还是独占模式获得的读写锁,都可以通过调用pthread_rwlock_unlock()函数进行释放该读写锁。


测试代码;

/*
 * pthread_rdlock.c
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

struct share{
	pthread_rwlock_t rwlock;
	int product;
};	//把共享数据和它们的同步变量集合到一个结构中,这往往是一个较好的编程技巧。

struct share *shareData;
void *produce(void *);
void *consume1(void *);
void *consume2(void *);

int main(void)
{
	pthread_t pid,cid1,cid2;
	int res;
	void *ret;

	srand(time(NULL));
	shareData = malloc(sizeof(struct share));
	if(shareData == NULL)
	{
		perror("shareData malloc fail");
		exit(1);
	}
	res = pthread_rwlock_init(&shareData->rwlock,NULL);
	if(res != 0)
	{
		perror("rwlock init fail");
		exit(1);
	}
	res = pthread_create(&pid,NULL,produce,NULL);
	if(res != 0)
	{
		perror("pthread produce create fail");
		exit(1);
	}
	res = pthread_create(&cid1,NULL,consume1,NULL);
	if(res != 0)
	{
		perror("pthread consume1 create fail");
		exit(1);
	}
	res = pthread_create(&cid2,NULL,consume2,NULL);
	if(res != 0)
	{
		perror("pthread consume2 create fail");
		exit(1);
	}

	res = pthread_join(pid,&ret);
	if(res == 0)
		printf("producer exit\n");
	res = pthread_join(cid1,&ret);
	if(res == 0)
		printf("consumer1 exit\n");
	res = pthread_join(cid2,&ret);
	if(res == 0)
		printf("consumer2 exit\n");
	pthread_rwlock_destroy(&shareData->rwlock);
	exit(0);
}

void *produce(void *arg)
{
	int i;
	for(i=0;i<6;i++)
	{
		pthread_rwlock_wrlock(&shareData->rwlock);
		shareData->product = rand()%100+1;
		printf("produce:%d\n",shareData->product);
		pthread_rwlock_unlock(&shareData->rwlock);
		sleep(1);
	}
	pthread_exit(NULL);
}

void *consume1(void *arg)
{
	int i;
	for(i=0;i<6;i++)
	{
		pthread_rwlock_rdlock(&shareData->rwlock);
		printf("consume1:%d\n",shareData->product);
		pthread_rwlock_unlock(&shareData->rwlock);
		sleep(1);
	}
	pthread_exit(NULL);
}

void *consume2(void *arg)
{
	int i;
	for(i=0;i<6;i++)
	{
		pthread_rwlock_rdlock(&shareData->rwlock);
		printf("consume2:%d\n",shareData->product);
		pthread_rwlock_unlock(&shareData->rwlock);
		sleep(1);
	}
	pthread_exit(NULL);
}

运行结果:

consume2:0
consume1:0
produce:10
consume1:10
consume2:10
produce:23
consume1:23
consume2:23
produce:4
consume2:4
consume1:4
produce:55
consume1:55
consume2:55
produce:87
consume2:87
consume1:87
produce:63
producer exit
consumer1 exit
consumer2 exit

注意:如果要实现生产-消费者模型,则消费者线程还应该加上条件变量,当共享区域没有数据时,阻塞等待。


如果把consume1的解锁注释掉,如下:

void *consume1(void *arg)
{
	int i;
	for(i=0;i<6;i++)
	{
		pthread_rwlock_rdlock(&shareData->rwlock);
		printf("consume1:%d\n",shareData->product);
		//pthread_rwlock_unlock(&shareData->rwlock);
		sleep(1);
	}
	pthread_exit(NULL);
}
运行结果:

consume2:0
consume1:0
consume2:0
consume1:0
consume1:0
consume2:0
consume2:0
consume1:0
consume1:0
consume2:0
consume2:0
consume1:0


可以看出,当生产者线程试图以写模式加锁时,由于consume1没有释放读锁,所以会一直阻塞;而consume2以读模式则依然可以成功加锁。

你可能感兴趣的:(多线程,linux,同步)