封装pthread_rwlock读写锁,实现嵌套调用

版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net/10km/article/details/82969947

pthread_rwlock简介

pthread_rwlock 读写锁与pthread_mutex类似,但rwlock允许更高的并行性。mutex只有两种状态:加锁状态,不加锁状态,而且一次只有一个线程对其加锁。 rwlock可以有三种状态:读取模式加锁状态,写入模式加锁状态,不加锁状态。 在写入模式加锁时与mutex一样,是独占的,一次只有一个线程可以占有写模式的读写锁。 在读取模式加锁状态,允许多个线程可用同时共享读模式的读写锁。

rwlock嵌套调用

何为嵌套调用? 函数A中有申请/释放锁的调用,函数B也有申请/释放锁的调用。而且A在加锁状态会调用B,这样对于锁就来说就会在已经加锁的状态下再次申请加锁,这就是嵌套调用。

根据POSIX 的定义,rwlock的读取锁允许在同一个线程中嵌套调用,但写入锁不允许嵌套调用。并且写入锁与读取锁是互斥的,所以在写入锁状态下不可以再申请读取锁,反之在读取错状态下也不能再申请写入锁。

不允许读取模式下调用写入锁,这个可以理解,但pthread_rwlock不允许嵌写入锁套调用,在实际应用中挺麻烦的。对于一个应用中可能有多个API申请rwlock锁,这些API之间又互相有调用关系,这里如果能允许rwlock嵌套调用,程序设计上会简单许多。

为了解决这个问题,我基于线程局部变量(thread local storage)对pthread_rwlock又封装了一层,实现允许嵌套调用的nest_rwlocknest_rwlock不仅允许读取锁嵌套调用,也允许写入锁嵌套调用,还允许在写入锁状态嵌套调用读取锁。实现原理就是利用TLS变量记录当前线程的读取锁和写入锁的嵌套计数,根据嵌套计数决定是否执行加锁解锁(pthread_rwlock_wrlock,pthread_rwlock_unlock),对于写入锁状态下,本来就是独占模式,本就不需要申请读取锁,所以只要当线程知道自己的加锁状态,就可以判断是否执行加锁解锁。

实现nest_rwlock所用的数据结构很简单,如下:

typedef struct _nest_rwlock_t{
	int rd_nest; /** 读取锁嵌套计数 */
	int wr_nest; /** 写入锁嵌套计数 */
	pthread_rwlock_t* rwlock;
}nest_rwlock_t;

rd_nest,wr_nest分别记录当前线程下读取锁和写入锁的嵌套计数,每次加锁,对应的计数器会加1,解锁时计数器会减1. 对于写入锁,当执行加锁时,会判断,wr_nest是否为0,为0时,代表是最外层的申请,这时会执行pthread_rwlock_wrlock执行加锁,否则只会将wr_nest加1,解锁时也一样,先将wr_nest减1,然后判断wr_nest是否为0,为0代表已经是嵌套调用的最外层,这时才执行pthread_rwlock_unlock. 对于读取锁,因为pthread_rwlock本身就支持嵌套调用,所以每次调用加锁都会执行pthread_rwlock_rdlock,同时计数器加1,解锁时也同样会执行pthread_rwlock_unlock并将计数器减1.但rd_nest并不是无用的,它代表了rwlock的加锁状态,当rd_nest不为0时代表rwlock在读取锁状态. 同样的道理当wr_nest不为0时代表rwlock在写入锁状态。 如果两个计数器都为0,代表rwlock在不加锁状态。 如果两个计数器都不为0,那程序的逻辑肯定错了。

NOTE: nest_rwlock_t对象在使用时必须定义为TLS变量(thread local storage)才能正确工作。参见后面的调用示例。

实现代码

下面是实现代码,详见代码中的注释。 nest_rwlock.c

/*
 * nest_rwlock.c
 *
 *  Created on: Oct 8, 2018
 *      Author: gyd
 */

#include <errno.h>
#include "nest_rwlock.h"
/**
 * @brief 对象初始化
 * 本函数不负责对 pthread_rwlock_t 对象的初始化
 * @param nest_rwlock
 * @param rwlock 已始化的 pthread_rwlock_t 对象
 * @return 0 if success,otherwise error code
 */
int nest_rwlock_init(nest_rwlock_t*nest_rwlock,pthread_rwlock_t* rwlock)
{
	int ret = 0;
	if(NULL == nest_rwlock || NULL == rwlock)return EINVAL;
	/** 不为NULL 代表已经初始化直接返回 */
	if(nest_rwlock->rwlock) return 0;
	nest_rwlock->rwlock = rwlock;
	nest_rwlock->rd_nest = 0;
	nest_rwlock->wr_nest = 0;
	return ret;
}
/**
 * @brief 对象销毁
 * 每个线程都要执行,本函数不负责对 pthread_rwlock_t 对象的销毁
 * @param nest_rwlock
 * @return 0 if success,otherwise error code
 */
int nest_rwlock_destroy(nest_rwlock_t*nest_rwlock)
{
	if(NULL == nest_rwlock)
		return EINVAL;
	nest_rwlock->rd_nest = 0;
	nest_rwlock->wr_nest = 0;
	nest_rwlock->rwlock = NULL;
	return 0;
}
/**
 * 获取读取锁
 * @param nest_rwlock
 * @return 返回 pthread_rwlock_rdlock 的返回值
 */
int nest_rwlock_rdlock(nest_rwlock_t*nest_rwlock)
{
	int ret;
	if(NULL == nest_rwlock)
		return EINVAL;
	/** 写锁状态下为独占资源不需要再加读取锁,直接返回 */
	if(nest_rwlock->wr_nest)
		return 0;

	/** 加锁成功计嵌套数器加1 */
	if(0 == (ret = pthread_rwlock_rdlock(nest_rwlock->rwlock)))
		++ nest_rwlock->rd_nest;
	return ret;
}
/**
 * 释放读取锁
 * @param nest_rwlock
 * @return 返回 pthread_rwlock_unlock 的返回值
 */
int nest_rwlock_rdunlock(nest_rwlock_t*nest_rwlock)
{
	int ret;
	if(NULL == nest_rwlock)
		return EINVAL;
	/** 写锁状态下没有加读取锁,直接返回 */
	if(nest_rwlock->wr_nest)
		return 0;
	/** 未加锁状态报错 */
	if(0 == nest_rwlock->rd_nest)
		return EPERM;

	/** 解锁成功计嵌套数器减1 */
	if(0 == (ret = pthread_rwlock_unlock(nest_rwlock->rwlock)))
		-- nest_rwlock->rd_nest;
	return ret;
}
/**
 * 获取写入锁
 * @param nest_rwlock
 * @return 返回 pthread_rwlock_wrlock 的返回值
 */
int nest_rwlock_wrlock(nest_rwlock_t*nest_rwlock)
{
	int ret = 0;
	if(NULL == nest_rwlock)
		return EINVAL;
	/** 已经加读取锁时报错 */
	if(nest_rwlock->rd_nest)
		return EDEADLK;

	/** 写入锁嵌套计数为0时执行解锁,否则直接将嵌套计数器减1 */
	if(0 == nest_rwlock->wr_nest)
		ret = pthread_rwlock_wrlock(nest_rwlock->rwlock);
	if(0 == ret)
		++ nest_rwlock->wr_nest;
	return ret;
}
/**
 * 释放写入锁
 * @param nest_rwlock
 * @return 返回 pthread_rwlock_unlock 的返回值
 */
int nest_rwlock_wrunlock(nest_rwlock_t*nest_rwlock)
{
	int ret = 0;
	if(NULL == nest_rwlock)
		return EINVAL;
	/** 已经加读取锁时报错 */
	if(nest_rwlock->rd_nest)
		return EDEADLK;
	/** 未加锁状态报错 */
	if(0 == nest_rwlock->wr_nest)
		return EPERM;
	-- nest_rwlock->wr_nest;
	/** 写入嵌套计数器为0时执行解锁 */
	if(0 == nest_rwlock->wr_nest)
		ret = pthread_rwlock_unlock(nest_rwlock->rwlock);

	return ret;
}
/**
 * 自动判断读写锁状态执行对应的解锁函数(pthread_rwlock_unlock,pthread_rwlock_unlock)
 * @param nest_rwlock
 * @return
 */
int nest_rwlock_unlock(nest_rwlock_t*nest_rwlock)
{
	if(NULL == nest_rwlock)
		return EINVAL;
	/** 已经加读取锁时执行读取锁解锁 */
	if(nest_rwlock->rd_nest && 0 == nest_rwlock->wr_nest)
		return nest_rwlock_rdunlock(nest_rwlock);
	/** 已经加写入锁时执行写入锁解锁 */
	if(nest_rwlock->wr_nest && 0 == nest_rwlock->rd_nest)
		return nest_rwlock_wrunlock(nest_rwlock);
	return EPERM;
}

nest_rwlock.h

/*
 * nest_rwlock.h
 *
 *  Created on: Oct 8, 2018
 *      Author: gyd
 */

#ifndef SRC_CORE_THREADSAFE_NEST_RWLOCK_H_
#define SRC_CORE_THREADSAFE_NEST_RWLOCK_H_
#include <pthread.h>

/**
 * @brief 基于 pthread_rwlock_t 实现读写锁嵌套调用封装
 * 读写锁都可以嵌套调用,
 * 写入锁状态下可以嵌套调用读取锁,读取锁状态下不可嵌套调用写入锁
 * 必须定义为TLS变量(thread local storage)
 */
typedef struct _nest_rwlock_t{
	int rd_nest; /** 读取锁嵌套计数 */
	int wr_nest; /** 写入锁嵌套计数 */
	pthread_rwlock_t* rwlock;
}nest_rwlock_t;

#ifdef __cplusplus
extern "C" {
#endif
int nest_rwlock_init(nest_rwlock_t*nest_rwlock,pthread_rwlock_t* rwlock);

int nest_rwlock_destroy(nest_rwlock_t*nest_rwlock);

int nest_rwlock_rdlock(nest_rwlock_t*nest_rwlock);

int nest_rwlock_rdunlock(nest_rwlock_t*nest_rwlock);

int nest_rwlock_wrlock(nest_rwlock_t*nest_rwlock);

int nest_rwlock_wrunlock(nest_rwlock_t*nest_rwlock);

int nest_rwlock_unlock(nest_rwlock_t*nest_rwlock);
#ifdef __cplusplus
}
#endif
#endif /* SRC_CORE_THREADSAFE_NEST_RWLOCK_H_ */

测试程序及调用示例

测试程序及调用示例, linux gcc,windows MSVC/MinGW测试通过,windows 下MSVC编译需要POSIX Threads (pthreads) for Win32支持。 test_rwlock.c

/*
 * test_rwlock.c
 *
 *  Created on: Oct 8, 2018
 *      Author: gyd
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nest_rwlock.h"

#if _MSC_VER
#define __TLS __declspec(thread)
#else
#define __TLS __thread
#endif
#define MAXDATA     1024
#define MAXREDER    100
#define MAXWRITER   100
struct
{
    pthread_rwlock_t   rwlock;
    char datas[MAXDATA];
} shared;
/** TLS变量 */
static __TLS nest_rwlock_t tls_lock;
static inline int is_empty_line(const char*str)
{
	return 0 == strcmp(str,"\n") || 0 == strcmp(str,"\r\n");
}
/** 返回当前线程id */
static inline unsigned int pthread_id()
{
#ifdef PTW32_VERSION
	/** 使用 prthread for win32 版本的pthread*/
	return pthread_getw32threadid_np(pthread_self());
#else
	return (unsigned int)pthread_self();
#endif
}
/**  统计shared.datas中的单词数量,用于嵌套测试 */
static int world_count()
{
	nest_rwlock_init(&tls_lock,&shared.rwlock);
	nest_rwlock_rdlock(&tls_lock);
    const char* str = shared.datas;
    const char* sep = " \n,!;?:+";

    unsigned int cnt = 0;
    do {
       str = strpbrk(str, sep); // find separator
       if(str) str += strspn(str, sep); // skip separator
       ++cnt; // increment word count
    } while(str && *str);
	nest_rwlock_unlock(&tls_lock);
	return cnt;
}
static void *reader(void *arg)
{
	int emp;
	printf("Reader beginning read message.thread 0x%x\n",pthread_id());
	/** 初始化tls变量 */
	nest_rwlock_init(&tls_lock,&shared.rwlock);

	const char* str = shared.datas;
	do
	{
		nest_rwlock_rdlock(&tls_lock);
		if(!(emp = is_empty_line(str)) && strlen(str))
		{
			printf("thread 0x%x Read message is: %s\n",pthread_id(),str);
			str += strlen(str);
	        /** 嵌套调用测试 */
	        printf("total %u words in shared buffer\n", world_count());
		}
		nest_rwlock_unlock(&tls_lock);
		 /** 输入空行结束循环  */
	}while(!emp);
	printf("Reader stope.thread 0x%x\n",pthread_id());
    return NULL;
}

static void *writer(void *arg)
{
    char datas[MAXDATA];
    printf("Writers beginning write message.thread 0x%x\n",pthread_id());
    /** 初始化tls变量 */
   	nest_rwlock_init(&tls_lock,&shared.rwlock);

    do
    {
        printf("Enter the write message: thread:0x%x\n",pthread_id());
        fgets(datas,sizeof(datas),stdin);
        nest_rwlock_wrlock(&tls_lock);
        strcat(shared.datas,datas);
        /** 嵌套调用测试 */
        printf("total %u words in shared buffer\n", world_count());
        nest_rwlock_unlock(&tls_lock);
        /** 输入空行结束循环  */
    }while(! is_empty_line(datas));
    printf("Writers stop.thread 0x%x\n",pthread_id());

    return NULL;
}

int main(int argc,char *argv[])
{
    int i,reader_count,writer_count;
    pthread_t tid_reader[MAXREDER],tid_writer[MAXWRITER];
    if(argc != 3)
    {
        printf("usage : test_rwlock  ${readercount} ${writercount}\n"
        		"    Enter empty line to finish program \n");
        exit(0);
    }
    reader_count = atoi(argv[1]);
    writer_count = atoi(argv[2]);
    printf("reader_count=%d,writer_count=%d\n",reader_count,writer_count);
    pthread_rwlock_init(&shared.rwlock,NULL);
    /** 启动写线程 */
    for(i=0; i<writer_count; ++i)
        pthread_create(&tid_writer[i],NULL,writer,NULL);
    /** 启动读线程 */
    for(i=0; i<reader_count; ++i)
        pthread_create(&tid_reader[i],NULL,reader,NULL);
    for(i=0; i<writer_count; ++i)
        pthread_join(tid_writer[i],NULL);
    for(i=0; i<reader_count; ++i)
        pthread_join(tid_reader[i],NULL);
    pthread_rwlock_destroy(&shared.rwlock);
    exit(0);
}

参考资料

http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_rwlock_rdlock.html http://pubs.opengroup.org/onlinepubs/009696899/basedefs/pthread.h.html https://www.cnblogs.com/diegodu/p/3890450.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券