一、读写锁介绍
1. 读写锁的基本概念
读写锁是从互斥锁中发展下来的,读写锁将访问中的读操作和写操作区分开来对待,把对资源的共享者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。在某些读数据比改数据频繁的应用中,读写锁将会比互斥锁表现出很大的优越性。
2. 读写锁的状态
读写锁与互斥量类似。 但读写锁有更高的并行性,其特性为:写独占,读共享。
- 读写锁有三种状态,读模式下加锁(共享)、写模式下加锁(独占)以及不加锁。
- 一次只有一个线程可以占有写模式下的读写锁;但是多个线程可以同时占有读模式下的读写锁。
- 读写锁在写加锁状态时,其他试图以写状态加锁的线程都会被阻塞。读写锁在读加锁状态时,如果有线程希望以写模式加锁时,必须阻塞,直到所有线程释放锁。
- 当读写锁以读模式加锁时,如果有线程试图以写模式对其加锁,那么读写锁会阻塞随后的读模式锁请求,以避免读锁长期占用,而写锁得不到请求。
3. 读写锁特性
-
读写锁是"写模式加锁"时, 解锁前,所有对该锁加锁的线程都会被阻塞。
-
读写锁是"读模式加锁"时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
-
读写锁是"读模式加锁"时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高
-
读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。
-
读写锁非常适合于对数据结构读的次数远大于写的情况。
二、读写锁的创建和销毁
#inlcude <pthread.h>
//读写锁初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
//销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
/***使用方法***/
//读写锁的声明
pthread_rwlock_t rwlock;
//读写锁的初始化
pthread_rwlock_init (&rwlock,NULL);
//读写锁的销毁
pthread_rwlock_destroy(pthread_rwlock_t *lock);
- 函数参数:
rwlock:读写锁
attr:读写锁属性 - 返回值:成功返回0,出错返回错误编号
三、读写锁的加锁和解锁
//读模式加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
//写模式加锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
- 函数参数:
rwlock:读写锁 - 返回值:
成功返回 0;出错,返回错误编号
读写锁特性的案例验证
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
//定义读写锁
pthread_rwlock_t rwlock;
int main(int argc,char* argv[])
{
//判断输入参数是否符合要求
if(argc<3)
{
printf("usrage:%s [r|w|]\r\n",argv[0]);
exit(1);
}
//初始化读写锁
pthread_rwlock_init(&rwlock,NULL);
//根据输入的参数第一次加读写锁
if(!strcmp("r",argv[1])){
//加读锁
if(pthread_rwlock_rdlock(&rwlock)!=0){
printf("First read lock failure!\r\n");
}
else {
printf("First read lock success!\r\n");
}
}
if(!strcmp("w",argv[1])){
//加读锁
if(pthread_rwlock_wrlock(&rwlock)!=0){
printf("First write lock failure!\r\n");
}
else {
printf("First read write success!\r\n");
}
}
//根据输入的参数第二次加读写锁
if(!strcmp("r",argv[2])){
//加读锁
if(pthread_rwlock_rdlock(&rwlock)!=0){
printf("First read lock failure!\r\n");
}
else {
printf("First read lock success!\r\n");
}
}
if(!strcmp("w",argv[2])){
//加读锁
if(pthread_rwlock_wrlock(&rwlock)!=0){
printf("First write lock failure!\r\n");
}
else {
printf("First read write success!\r\n");
}
}
//加几次锁,需要解几次锁
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
//销毁创建的读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}
由运行结果可以知道,当都上读锁的时候,都可以上锁成功;先上读锁,再上写锁,写锁会阻塞;先上写锁后,不管是上读锁还是上写锁,都会失败。(红框中的提示可能与环境有关,暂时不管)
即验证了读写锁特性:1. 读和读:不排斥 2. 读和写:排斥 3. 写和写:排斥
四、案例改进
#ifndef __ATM_ACCOUNT_H__
#define __ATM_ACCOUNT_H__
#include <math.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
/** 账户信息 */
typedef struct {
int code; ///< 银行账户的编码
double balance; ///< 账户余额
/** 建议互斥锁用来锁定一个账户(共享资源),和账户绑定再一起,
* 尽量不设置成全局变量,否则可能出现一把锁去锁几百个账户,导致并发性能降低 */
//pthread_mutex_t mutex; ///< 定义一把互斥锁,用来对多线程操作的银行账户(共享资源)进行加锁
pthread_rwlock_t rwlock; ///<定义读写锁
}atm_Account;
/** 创建账户 */
extern atm_Account *atm_account_Create(int code, double balance);
/** 销毁账户 */
extern void atm_account_Destroy(atm_Account *account);
/** 取款 */
extern double atm_account_Withdraw(atm_Account *account, double amt);
/** 存款 */
extern double atm_account_Desposit(atm_Account *account, double amt);
/** 查看账户余额 */
extern double atm_account_BalanceGet(atm_Account *account);
#endif
#include "atm_account.h"
/** 创建账户 */
atm_Account *atm_account_Create(int code, double balance)
{
atm_Account *account = (atm_Account *)malloc(sizeof(atm_Account));
if(NULL == account) {
return NULL;
}
account->code = code;
account->balance = balance;
/** 对互斥锁进行初始化 */
//pthread_mutex_init(&account->mutex, NULL);
/** 初始化读写锁 */
pthread_rwlock_init(&account->rwlock, NULL);
return account;
}
/** 销毁账户 */
void atm_account_Destroy(atm_Account *account)
{
if(NULL == account){
return ;
}
//pthread_mutex_destroy(&account->mutex);
pthread_rwlock_destroy(&account->rwlock); ///< 销毁读写锁
free(account);
}
/** 取款: 成功,则返回取款金额 */
double atm_account_Withdraw(atm_Account *account, double amt)
{
if(NULL == account) {
return 0.0;
}
/** 对共享资源(账户进行加锁) */
//pthread_mutex_lock(&account->mutex);
pthread_rwlock_wrlock(&account->rwlock); ///< 加写锁
if(amt < 0 || amt > account->balance) {
//pthread_mutex_unlock(&account->mutex);
pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁
return 0.0;
}
double balance_tmp = account->balance;
sleep(1);
balance_tmp -= amt;
account->balance = balance_tmp;
//pthread_mutex_unlock(&account->mutex);
pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁
return amt;
}
/** 存款: 返回存款的金额 */
double atm_account_Desposit(atm_Account *account, double amt)
{
if(NULL == account){
return 0.0;
}
/** 对共享资源(账户进行加锁) */
//pthread_mutex_lock(&account->mutex);
pthread_rwlock_wrlock(&account->rwlock); ///< 加写锁
if(amt < 0){
//pthread_mutex_unlock(&account->mutex);
pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁
return 0.0;
}
double balance_tmp = account->balance;
sleep(1);
balance_tmp += amt;
account->balance = balance_tmp;
//pthread_mutex_unlock(&account->mutex);
pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁
return amt;
}
/** 查看账户余额 */
double atm_account_BalanceGet(atm_Account *account)
{
if(NULL == account){
return 0.0;
}
/** 对共享资源(账户进行加锁) */
//pthread_mutex_lock(&account->mutex);
pthread_rwlock_rdlock(&account->rwlock); ///< 上读锁
double balance_tmp = account->balance;
//pthread_mutex_unlock(&account->mutex);
pthread_rwlock_unlock(&account->rwlock); ///< 释放写锁
return balance_tmp;
}
从运行结果来看,使用读写锁和使用互斥锁运行的结果是一样的。