共享锁也称为读写锁,其特点是读和读不互斥,读写互斥,写写互斥。在多线程开发中一般使用在多个读单个写入的场景,比如将摄像头的画面读取后显示并同时编码推流。c语言做跨平台开发,实现一个共享锁封装对象还是有用的。
一、接口设计:
1、数据结构:
共享锁一般是在互斥锁的基础上实现的,所以需要用到互斥锁,这里直接使用了我另外一篇文章《c语言跨平台共享锁封装》作为互斥锁,以减少一些宏代码。为了灵活内存选择策略,直接定义字段在头文件,不采用pimp的方式。
#include "acf_mutex.h"
/// <summary>
/// 共享锁对象
/// </summary>
typedef struct {
/// <summary>
/// 读互斥锁
/// </summary>
acf_mutex readerMutex;
/// <summary>
/// 互斥锁
/// </summary>
acf_mutex mutex;
/// <summary>
/// 读的数量
/// </summary>
int readerCount;
} acf_shared_mutex;
2、方法
(1)、初始化
共享锁对象使用前需要初始化,传入共享锁对象的指针。
/// <summary>
/// 初始化
/// </summary>
/// <param name="mutex">共享锁对象的指针</param>
void acf_shared_mutex_init(acf_shared_mutex* mutex);
(2)、反初始化
共享锁对象销毁前需要反初始化,传入共享锁对象的指针。
/// <summary>
/// 反初始化
/// </summary>
/// <param name="mutex">共享锁对象的指针</param>
void acf_shared_mutex_deinit(acf_shared_mutex* mutex);
(3)、加读锁
以读锁的方式进入,多线程环境中调用此方法,第一个线程进入读锁的后,其他线程调用此方法同样可以进入锁,但是如果调用读锁则会等待。
/// <summary>
/// 加共享锁
/// </summary>
/// <param name="mutex">共享锁对象的指针</param>
void acf_shared_mutex_share_lock(acf_shared_mutex* mutex);
(4)、尝试加读锁
尝试进入读锁而不等待,直接返回,返回1进入成功,0进入失败。
/// <summary>
/// 尝试加共享(读)锁
/// </summary>
/// <param name="mutex">共享锁对象的指针</param>
/// <returns>1成功,0失败</returns>
int acf_shared_mutex_try_share_lock(acf_shared_mutex* mutex);
(5)、解读锁
执行完成读操作后,需要调用解读锁退出,将资源让出给其他线程使用。注意,“解读锁”必须与“加读锁”对应,不能与写锁混合使用。
/// <summary>
/// 解读锁
/// </summary>
/// <param name="mutex"></param>
void acf_shared_mutex_share_unlock(acf_shared_mutex* mutex);
(6)、加写锁
以写锁的方式进入。多线程环境中调用此方法,只有不存在读锁以及其他写锁时才能进入,否则只能等待。有一个线程进入写锁的后,其他线程想进入读锁或者写锁都只能等待。
/// <summary>
/// 加写锁
/// </summary>
/// <param name="mutex">共享锁对象的指针</param>
void acf_shared_mutex_lock(acf_shared_mutex* mutex);
(7)、尝试加写锁
尝试进入写锁而不等待,直接返回,返回1进入成功,0进入失败。
/// <summary>
/// 尝试加写锁
/// </summary>
/// <param name="mutex">共享锁对象的指针</param>
/// <returns>1成功,0失败</returns>
int acf_shared_mutex_try_lock(acf_shared_mutex* mutex);
(8)、解写锁
执行完成写操作后,需要调用解写锁退出,将资源让出给其他线程使用。注意,“解写锁”必须与“加写锁”对应,不能与读锁混合使用。
/// <summary>
/// 解写锁
/// </summary>
/// <param name="mutex">共享锁对象的指针</param>
void acf_shared_mutex_unlock(acf_shared_mutex* mutex);
二、关键实现
1、读锁进入
读锁进入时需要先记录读锁的数量,第一个读锁进入时需要加互斥锁,以确保和写锁互斥,后面的读锁进入则只需要加计数即可。
void acf_shared_mutex_share_lock(acf_shared_mutex * _this) {
acf_mutex_lock(&_this->readerMutex);
_this->readerCount++;
if (_this->readerCount == 1)
{
acf_mutex_lock(&_this->mutex);
}
acf_mutex_unlock(&_this->readerMutex);
}
2、读锁退出
读锁退出时需要先更新读锁的数量,最后一个读锁退出需要解互斥锁,确保其他锁能够正常进入。
void acf_shared_mutex_share_unlock(acf_shared_mutex * _this) {
acf_mutex_lock(&_this->readerMutex);
_this->readerCount--;
if (_this->readerCount == 0)
{
acf_mutex_unlock(&_this->mutex);
}
acf_mutex_unlock(&_this->readerMutex);
}
三、使用例子
#include"Thread/acf_thread.h"
#include"Thread/acf_shared_mutex.h"
#include<stdio.h>
static acf_shared_mutex _mutex;
static void write(void* arg) {
printf("thread %d request write lock\n", (int)arg);
//进入写锁
acf_thread_sleep(100);
acf_shared_mutex_lock(&_mutex);
printf("thread %d got write lock\n", (int)arg);
acf_thread_sleep(3000);
//退出写锁
acf_shared_mutex_unlock(&_mutex);
printf("thread %d leave write lock\n", (int)arg);
}
static void read(void* arg) {
printf("thread %d request read lock\n", (int)arg);
acf_thread_sleep(200);
//进入写锁
acf_shared_mutex_share_lock(&_mutex);
printf("thread %d got read lock\n", (int)arg);
acf_thread_sleep(2000);
//退出写锁
acf_shared_mutex_share_unlock(&_mutex);
printf("thread %d leave read lock\n", (int)arg);
}
int main(int argc, char** argv) {
//初始化共享锁
acf_shared_mutex_init (&_mutex);
//创建写线程1
acf_thread thead1 = acf_thread_create(write, 1);
//创建读线程2
acf_thread thead2 = acf_thread_create(read, 2);
//创建读线程3
acf_thread thead3 = acf_thread_create(read, 3);
acf_thread_wait_destroy(thead1);
acf_thread_wait_destroy(thead2);
acf_thread_wait_destroy(thead3);
//反初始化共享锁
acf_shared_mutex_deinit(&_mutex);
return 0;
}
四、完整代码