引言

  读写锁 是为了 解决, 大量 ''读'' 和 少量 ''写'' 的业务而设计的.

读写锁有3个特征:

  1.当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞
  2.当读写锁在读加锁状态时,再以读模式对它加锁的线程都能得到访问权,但以写模式加锁的线程将会被阻塞
  3.当读写锁在读加锁状态时,如果有线程试图以写模式加锁,读写锁通常会阻塞随后的读模式加锁

我们先举一段标准库构建的读写锁demo来了解读写锁api 的使用 .

pthread_rwlock.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> #define _INT_BZ (13)
#define _INT_WTH (2)
#define _INT_RTH (10) struct rwarg {
pthread_t id;
pthread_rwlock_t rwlock; // 加锁用的
int idx; // 指示buf中写到那了
char buf[BUFSIZ]; // 存储临时数据
}; // 写线程, 主要随机写字符进去
void twrite(struct rwarg * arg);
// 读线程
void treads(struct rwarg * arg); /*
* 主函数测试线程读写逻辑
* 少量写线程, 大量读线程测试
*/
int main(int argc, char * argv[]) {
// 初始化定义需要使用的量. C99以上写法, 避免跨平台不同实现的警告问题, 感谢好人随性徜徉
struct rwarg arg = { , .rwlock = PTHREAD_RWLOCK_INITIALIZER, , "" };
int i; // 读线程跑起来
for(i=; i<_INT_RTH; ++i)
pthread_create((pthread_t *)&arg, NULL, (void * (*)(void *))treads, &arg); // 写线程再跑起来
for(i=; i<_INT_WTH; ++i)
pthread_create((pthread_t *)&arg, NULL, (void * (*)(void *))twrite, &arg); // 简单等待一下
printf("sleep input enter:");
getchar(); return ;
} // 写线程, 主要随机写字符进去
void
twrite(struct rwarg * arg) {
pthread_detach(pthread_self()); pthread_rwlock_wrlock(&arg->rwlock);
while(arg->idx < _INT_BZ) {
arg->buf[arg->idx] = 'a' + arg->idx;
++arg->idx;
}
pthread_rwlock_unlock(&arg->rwlock);
} // 读线程
void
treads(struct rwarg * arg) {
pthread_detach(pthread_self()); while(arg->idx < _INT_BZ) {
pthread_rwlock_rdlock(&arg->rwlock);
puts(arg->buf);
pthread_rwlock_unlock(&arg->rwlock);
}
}

编译

gcc -Wall -ggdb2 -o pthread_rwlock.out pthread_rwlock.c -lpthread

执行的结果

linux上执行没反应, 这代码放在window 同样没有反应. 主要原因是 大量读加锁阻塞了写加锁.

预估主要原因是 pthread 实现的 rwlock 读写锁, 没有粗暴支持读写锁特性3 . 这也是其读写锁一个潜在bug(写锁没有阻塞后续的读锁).

是不是有些收获, 底层API有问题也不少的. 哈哈.

这里扯一点C基础语法 中 I 对于 美得 感受与写法. 希望大家有思考.

a) 指针一般 写法

int *piyo;

// 声明部分
int *heoo(int a, int *pi); // 定义部分
int *
heoo(int a, int b) {
...
}

上面是一种通用写法.  缺点在 声明和 定义 不统一, 不协调, 不爽.

b) 指针仿照OOP 写法

int* piyo;

// 声明部分
int* heoo(int a, int* pi); //定义部分
int*
heoo(int a, int b){
...
}

这种写法很好理解, 也很好看. 可惜这是C, (C++也是). 因为C出现比较早, 存在缺陷. 上面致命缺点是

int* piyo, *hoge;

特别丑.

c) 请用下面写法, 都是从无数别人代码中磨出来的.

int * piyo;

// 声明部分
int * heoo(int a, int * pi); //定义部分
int *
heoo(int a, int b){
...
}

到这里扯淡结束了.  编程 希望是  实用->设计好->有美感->自然 . 而不是 屎一样的实现而妄想优雅的接口. 如果有一天能为自己写代码的话.

前言

到这里我们按照上面读写锁的3条特性, 自己实现一个读写锁.  首先看数据结构

// init need 0
struct rwlock {
int rlock;
int wlock;
};

当我们需要使用读写锁时候只需要 struct rwlock lock = { 0 , 0 }; 很清洁

后面先在linux 使用gcc 提供的原子操作特性实现一个 读锁 实现

// 加读锁
static void rwlock_rlock(struct rwlock * lock) {
for(;;) {
// 看是否有人在试图读, 得到并防止代码位置优化
while(lock->wlock)
__sync_synchronize();
__sync_add_and_fetch(&lock->rlock, );
// 没有写占用, 开始读了
if(!lock->wlock)
break;
// 还是有写, 删掉添加的读
__sync_add_and_fetch(&lock->rlock, -);
}
}

在加写锁时候, 先判断读锁是否没有人在使用了. __sync_synchronize 是为了防止进行代码位置优化. 后面逻辑是

开始加读锁, 但是在加读锁瞬间如果有写锁那么立马释放刚申请的读锁. 一切为读锁为核心设计.

对于写锁

// 加写锁
static void rwlock_wlock(struct rwlock * lock) {
while(__sync_lock_test_and_set(&lock->wlock, ))
;
// 等待读占用锁
while(lock->rlock)
__sync_synchronize();
}

只考虑读锁互相竞争, 竞争好了之后, 开始等待读锁. 好理解, 后面释放相对容易. 扯一点对于pthread标准库读写锁只有一个释放接口. 估计内存有状态保持. 才能保证释放.

使用方便, 但是性能不好. 读写锁实现耦合又大了. 这里实现

// 解写锁
static inline void rwlock_wunlock(struct rwlock * lock) {
__sync_lock_release(&lock->wlock);
} // 解读锁
static inline void rwlock_runlock(struct rwlock * lock) {
__sync_add_and_fetch(&lock->rlock, -);
}

写锁 直接解锁到底, 读锁采用引用减少一处理. 看一个demo simple_rwlock.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> // init need 0
struct rwlock {
int rlock;
int wlock;
}; // 加读锁
static void rwlock_rlock(struct rwlock * lock) {
for(;;) {
// 看是否有人在试图读, 得到并防止代码位置优化
while(lock->wlock)
__sync_synchronize();
__sync_add_and_fetch(&lock->rlock, );
// 没有写占用, 开始读了
if(!lock->wlock)
break;
// 还是有写, 删掉添加的读
__sync_add_and_fetch(&lock->rlock, -);
}
} // 加写锁
static void rwlock_wlock(struct rwlock * lock) {
while(__sync_lock_test_and_set(&lock->wlock, ))
;
// 等待读占用锁
while(lock->rlock)
__sync_synchronize();
} // 解写锁
static inline void rwlock_wunlock(struct rwlock * lock) {
__sync_lock_release(&lock->wlock);
} // 解读锁
static inline void rwlock_runlock(struct rwlock * lock) {
__sync_add_and_fetch(&lock->rlock, -);
} // ------------------------ 下面是业务代码 ---------------------------------- #define _INT_BZ (13)
#define _INT_WTH (2)
#define _INT_RTH (10) struct rwarg {
pthread_t id;
struct rwlock lock; // 加锁用的
int idx; // 指示buf中写道那了
char buf[BUFSIZ]; // 存储临时数据
}; // 写线程, 主要随机写字符进去
void twrite(struct rwarg * arg);
// 读线程
void treads(struct rwarg * arg); /*
* 自己写读写锁底层
*/
int main(int argc, char * argv[]) {
// 初始化定义需要使用的量
struct rwarg arg = { };
int i; // 读线程跑起来
for(i=; i<_INT_RTH; ++i)
pthread_create((pthread_t *)&arg, NULL, (void * (*)(void *))treads, &arg); // 写线程再跑起来
for(i=; i<_INT_WTH; ++i)
pthread_create((pthread_t *)&arg, NULL, (void * (*)(void *))twrite, &arg); // 简单等待一下
printf("sleep input enter:");
getchar(); return ;
} // 写线程, 主要随机写字符进去
void
twrite(struct rwarg * arg) {
pthread_detach(pthread_self()); while(arg->idx < _INT_BZ) {
rwlock_wlock(&arg->lock);
arg->buf[arg->idx] = 'a' + arg->idx;
++arg->idx;
rwlock_wunlock(&arg->lock);
}
puts("twrite is exit...");
} // 读线程
void
treads(struct rwarg * arg) {
pthread_detach(pthread_self()); while(arg->idx < _INT_BZ) {
rwlock_rlock(&arg->lock);
puts(arg->buf);
rwlock_runlock(&arg->lock);
}
puts("treads is exit...");
}

代码就是上面读写锁实现加上上面例子构建的. 编译命令

gcc -Wall -ggdb2 -o simple_rwlock.out simple_rwlock.out -lpthread

执行结果

得到我们想要的结果, 说明这个读写锁至少满足了业务, 比pthread 好用并且功能完整. 后面再看正文扩展成通用库.

正文

  到这里前戏都已经快完毕, 需要到正文了.  通过上面细说, 我们能够封装一个跨平台的读写锁了. 看 scatom.c 

#ifndef _H_SIMPLEC_SCATOM
#define _H_SIMPLEC_SCATOM /*
* 作者 : wz
*
* 描述 : 简单的原子操作,目前只考虑 VS(CL) 小端机 和 gcc
* 推荐用 posix 线程库
*/ // 如果 是 VS 编译器
#if defined(_MSC_VER) #include <Windows.h> //忽略 warning C4047: “==”:“void *”与“LONG”的间接级别不同
#pragma warning(disable:4047) // v 和 a 都是 long 这样数据
#define ATOM_FETCH_ADD(v, a) InterlockedExchangeAdd((LONG volatile *)&(v), (LONG)(a)) #define ATOM_ADD_FETCH(v, a) InterlockedAdd((LONG volatile *)&(v), (LONG)(a)) #define ATOM_SET(v, a) InterlockedExchange((LONG volatile *)&(v), (LONG)(a)) #define ATOM_CMP(v, c, a) (c == InterlockedCompareExchange((LONG volatile *)&(v), (LONG)(a), (LONG)c)) /*
*对于 InterlockedCompareExchange(v, c, a) 等价于下面
*long tmp = v ; v == a ? v = c : ; return tmp;
*
*咱们的 ATOM_FETCH_CMP(v, c, a) 等价于下面
*long tmp = v ; v == c ? v = a : ; return tmp;
*/
#define ATOM_FETCH_CMP(v, c, a) InterlockedCompareExchange((LONG volatile *)&(v), (LONG)(a), (LONG)c) #define ATOM_LOCK(v) \
while(ATOM_SET(v, )) \
Sleep() #define ATOM_UNLOCK(v) ATOM_SET(v, 0) // 保证代码不乱序优化后执行
#define ATOM_SYNC() MemoryBarrier() // 否则 如果是 gcc 编译器
#elif defined(__GNUC__) #include <unistd.h> /*
* type tmp = v ; v += a ; return tmp ;
* type 可以是 8,16,32,64 bit的类型
*/
#define ATOM_FETCH_ADD(v, a) __sync_fetch_add_add(&(v), (a)) /*
* v += a ; return v;
*/
#define ATOM_ADD_FETCH(v, a) __sync_add_and_fetch(&(v), (a)) /*
* type tmp = v ; v = a; return tmp;
*/
#define ATOM_SET(v, a) __sync_lock_test_and_set(&(v), (a)) /*
* bool b = v == c; b ? v=a : ; return b;
*/
#define ATOM_CMP(v, c, a) __sync_bool_compare_and_swap(&(v), (c), (a)) /*
* type tmp = v ; v == c ? v = a : ; return v;
*/
#define ATOM_FETCH_CMP(v, c, a) __sync_val_compare_and_swap(&(v), (c), (a)) /*
*加锁等待,知道 ATOM_SET 返回合适的值
*_INT_USLEEP 是操作系统等待纳秒数,可以优化,看具体操作系统
*
*使用方式
* int lock = 0;
* ATOM_LOCK(lock);
*
* // to do think ...
*
* ATOM_UNLOCK(lock);
*
*/
#define _INT_USLEEP_LOCK (2)
#define ATOM_LOCK(v) \
while(ATOM_SET(v, )) \
usleep(_INT_USLEEP_LOCK) // 对ATOM_LOCK 解锁, 当然 直接调用相当于 v = 0;
#define ATOM_UNLOCK(v) __sync_lock_release(&(v)) // 保证代码不乱序
#define ATOM_SYNC() __sync_synchronize() #endif // !_MSC_VER && !__GNUC__ #ifndef _STRUCT_RWLOCK
#define _STRUCT_RWLOCK
/*
* 这里构建simple write and read lock
* struct rwlock need zero.
*/ // init need all is 0
struct rwlock {
int rlock;
int wlock;
}; // add read lock
static void rwlock_rlock(struct rwlock * lock) {
for (;;) {
// 看是否有人在试图读, 得到并防止代码位置优化
while (lock->wlock)
ATOM_SYNC(); ATOM_ADD_FETCH(lock->rlock, );
// 没有写占用, 开始读了
if (!lock->wlock)
break; // 还是有写, 删掉添加的读
ATOM_ADD_FETCH(lock->rlock, -);
}
} // add write lock
static void rwlock_wlock(struct rwlock * lock) {
ATOM_LOCK(lock->wlock);
// 等待读占用锁
while (lock->rlock)
ATOM_SYNC();
} // unlock write
static inline void rwlock_wunlock(struct rwlock * lock) {
ATOM_UNLOCK(lock->wlock);
} // unlock read
static inline void rwlock_runlock(struct rwlock * lock) {
ATOM_ADD_FETCH(lock->rlock, -);
} #endif // !_STRUCT_RWLOCK #endif // !_H_SIMPLEC_SCATOM

我们是可以直接在window上测试, 对于window上线程模型同样采用 pthread for win32.

测试文件 sc_template/sc_console_template/main/test_atom_rwlock.c

#include <stdio.h>
#include <scatom.h>
#include <pthread.h> #define _INT_BZ (13)
#define _INT_WTH (2)
#define _INT_RTH (10) struct rwarg {
pthread_t id;
struct rwlock lock; // 加锁用的
int idx; // 指示buf中写道那了
char buf[BUFSIZ]; // 存储临时数据
}; // 写线程, 主要随机写字符进去
void twrite(struct rwarg * arg);
// 读线程
void treads(struct rwarg * arg); /*
* 自己写读写锁底层
*/
int main(int argc, char * argv[]) {
// 初始化定义需要使用的量
int i;
struct rwarg arg = { }; // 读线程跑起来
for (i = ; i<_INT_RTH; ++i)
pthread_create((pthread_t *)&arg, NULL, (void * (*)(void *))treads, &arg); // 写线程再跑起来
for (i = ; i<_INT_WTH; ++i)
pthread_create((pthread_t *)&arg, NULL, (void * (*)(void *))twrite, &arg); // 简单等待一下
printf("sleep input enter:");
getchar(); return ;
} // 写线程, 主要随机写字符进去
void
twrite(struct rwarg * arg) {
pthread_detach(pthread_self()); while (arg->idx < _INT_BZ) {
rwlock_wlock(&arg->lock);
arg->buf[arg->idx] = 'a' + arg->idx;
++arg->idx;
rwlock_wunlock(&arg->lock);
}
puts("twrite is exit...");
} // 读线程
void
treads(struct rwarg * arg) {
pthread_detach(pthread_self()); while (arg->idx < _INT_BZ) {
rwlock_rlock(&arg->lock);
puts(arg->buf);
rwlock_runlock(&arg->lock);
}
puts("treads is exit...");
}

F7 -> Ctrl + F5 运行结果

一切正常. 后面将代码放入linux 上测试一下. 先看下面目录结构

编译命令

gcc -Wall -ggdb2 -I. -o test_atom_rwlock.out test_atom_rwlock.c -lpthread

执行结果 也是一切正常

这里 -I是为了附加指定的查找路径, -ggdb2 启用宏调试等级.  代码中存在 struct rwarg arg = { 0 };

其实使用了C初始化特性, 标注的按照标注的初始化, 未标注的直接按照零初始化.

后记

  错误是难免欢迎吐糙~~  (● ̄(エ) ̄●)

  烂泥 http://music.163.com/#/song?id=411314656

  

C基础 读写锁中级剖析的更多相关文章

  1. Go基础系列:互斥锁Mutex和读写锁RWMutex用法详述

    sync.Mutex Go中使用sync.Mutex类型实现mutex(排他锁.互斥锁).在源代码的sync/mutex.go文件中,有如下定义: // A Mutex is a mutual exc ...

  2. Java基础-Java中的并法库之重入读写锁(ReentrantReadWriteLock)

    Java基础-Java中的并法库之重入读写锁(ReentrantReadWriteLock) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在学习Java的之前,你可能已经听说过读 ...

  3. Go语言基础之13--线程安全及互斥锁和读写锁

    一.线程安全介绍 1.1 现实例子 A. 多个goroutine同时操作一个资源,这个资源又叫临界区 B. 现实生活中的十字路口,通过红路灯实现线程安全 C. 火车上的厕所(进去之后先加锁,在上厕所, ...

  4. 二、多线程基础-乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁

    1.10乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将 比较-设置 ...

  5. ReentrantReadWriteLock 读写锁解析

    4 java中锁是个很重要的概念,当然这里的前提是你会涉及并发编程. 除了语言提供的锁关键字 synchronized和volatile之外,jdk还有其他多种实用的锁. 不过这些锁大多都是基于AQS ...

  6. 读写锁ReadWriteLock

    为了提高性能,Java提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率. Java中读写锁有个接口java.util ...

  7. Linux:使用读写锁使线程同步

    基础与控制原语 读写锁 与互斥量类似,但读写锁允许更高的并行性.其特性为:写独占,读共享. 读写锁状态: 一把读写锁具备三种状态:     1. 读模式下加锁状态 (读锁)     2. 写模式下加锁 ...

  8. Golang之并发资源竞争(读写锁)

    前面的有篇文章在讲资源竞争的时候,提到了互斥锁.互斥锁的根本就是当一个goroutine访问的时候,其他goroutine都不能访问,这样肯定保证了资源的同步,避免了竞争,不过也降低了性能. 仔细剖析 ...

  9. Java之——redis并发读写锁,使用Redisson实现分布式锁

    原文:http://blog.csdn.net/l1028386804/article/details/73523810 1. 可重入锁(Reentrant Lock) Redisson的分布式可重入 ...

随机推荐

  1. c/c++中的关键字(static、const、inline、friend)

    static:1.a.c语言中static修饰的局部变量在编译时赋初始值,只赋初始值一次,在函数运行时已有初值,每次调用函数时不用重新赋值,指示保留上次 函 数调用结束时的值. 如果定义局部变量不赋初 ...

  2. BZOJ4247 挂饰(动态规划)

    相当于一个有负体积的背包.显然如果确定了选哪些,应该先把体积小的挂上去.于是按体积从小到大排序,就是一个裸的背包了. #include<iostream> #include<cstd ...

  3. hdu 1879 继续畅通工程 (最小生成树)

    继续畅通工程 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

  4. [UVA307]小木棍 Sticks

    题目大意:有一堆小木棍,把它们接成相同长度的小木棍,问结果的小木棍的最小长度是多少,多组数据 题解:$dfs$,各种剪枝. 卡点:无 C++ Code: #include <cstdio> ...

  5. BZOJ1604 & 洛谷2906:[USACO2008 OPEN]Cow Neighborhoods 奶牛的邻居——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=1604 https://www.luogu.org/problemnew/show/P2906#sub ...

  6. IE6“无法打开站点,已终止操作”提示的解决

    今天遇到一个问题,网站在IE 6下面打开会提示:Internet Explorer无法打开站点XXX.已终止操作. 先介绍一下网上常见的解决方法. 因为在页面还没有ready的时候就调用了htmlOb ...

  7. js ajax向后台传数组可以直接拼接传输

    ajax向后台传数组是可以直接传的,写法如下 var ids = [ ]; $(dom1).each(function(){ ids.push($(this).val()); }); var  use ...

  8. https客户端遇到过的问题

    1.用.p12格式的证书,在windows上调试完全没问题,在Linux服务器上,提示无效证书格式. 解决方法:将.p12格式的证书转换为.jks格式的证书. 将.p12格式的证书转换为.jks格式的 ...

  9. tp查找某字段,排除某字段,不用一次写那么多

    更多的情况下我们都是查询某些字段,但有些情况下面我们需要通过字段排除来更方便的查询字段,例如文章详细页,我们可能只需要排除status和update_time字段,这样就不需要写一堆的字段名称了(有些 ...

  10. C# 枚举的初始化

    3.2 枚举类型(Enum types)的默认值 对于枚举类型(Enum types),.NET会自动将字面值0(literal 0)隐式地转换为对应的枚举类型. 3.2.1 有一个0值成员 如果枚举 ...