- 浏览: 342500 次
- 性别:
- 来自: 福建福州
文章分类
最新评论
-
jw72jw:
最后这个是打表求值
LUA源码分析三:table分析(1) -
dyllove98:
"一些非常重要的问题,涉及面少。那这个时候,我更崇尚 ...
乱写:团队里的独裁和民主一点看法 -
jvmlover:
被踩10次了,什么思想感情啊。
LUA源码分析三:table分析(1) -
chenchenfly99:
chenchenfly99 写道
MMO游戏终极内测开服一周,问题记录 -
chenchenfly99:
...
MMO游戏终极内测开服一周,问题记录
以下代码和资料均学习自:《进程间通信》第8章读写锁
其中附件中的代码为自己重新封装后的代码和一个测试代码
编译环境如下
Thread model: posix
gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
正文:
读优先的读写锁策略
假设对于一个共享的数据区来说,读/写都需要,而写的并发又可能比较频繁一点,如果按照普通的mutex思路可能是以下结构:
对于write来说,对于共享的形式是独占(shared-exclusive);但对于读锁来说对于共享的形式是共享锁(shared lock),即不需要mutex的互斥保护。那么以上的写法中,对于读的情况效率大大的打折。比如一个银行帐户,多个线程可以同时读出某个帐户的收支结余,但是一旦有一个线程需要更新某个给定收支结余,该线程就必须等待所有读出者完成该收支结余的读出,然后只允许该更新线程修改这个收支结余。那么在实现这个策略时,必须遵循以下三个条件
1、 保证并发的读取,不对读取做什么界限的限制,仅仅以一个计数器来表示当前的读取量
2、 有写入线程时,保证写入线程的独立执行
3、 在有写入线程发生,以这个写入线程时间点为界限,之前的等待读线程要执行完,之后的读线程处于等待状态
那么可以设置这么一个结构体`
再设置两个获取读/写锁函数:
int pthread_RWlock_rdlock (pthread_RWlock_t *pRw);
int pthread_RWlock_wrlock (pthread_RWlock_t *pRw);
流程的判断变为如下的工作流程,这里我先把图给出,问题统一放在后面分析
获取读/写锁两个函数大体流程如下:
pthread_RWlock_wrlock函数流程如下:
pthread_RWlock_rdlock函数流程如下:
问题一:先说明一个技巧
这里用了phtread_cond_wait(), 防止一个不断的轮询过程,代码是这样的
注意,这里用了个while.在多线程的情况下,有多个cond_wait进行等待,当rw_mutex的锁被释放时,可能被其他线程抢到(即不保证被本身的线程取到),并对rw_refcount进行修改。如果不对rw_refcount进行判断而直接执行,就可能造成数据的不准确。
问题二:获取读锁时对rw_refcount!= 0的判断
这要和读锁的rw_refcount <0和rw_nwaitwriters>0两个条件结合起来进行解释。首先假设当前有多个线程进行工作,这时有个写线程进来,发生阻塞,waitwriters自加。这个时候比这个写线程后来的读线程检测到rw_nwaitwriters>0而发生阻塞,在这之前的读线程仍在工作,这里我们还需要知道pthread_RWlock_unlock的部分工作代码
看吧,把之前对读锁的获取时rw_refcount++和这里的rw_refcount—结合起来,可得知,当读线程都工作完毕时,rw_refcount必然会等于0,这也满足了我们开头提出的三个条件中的最后一条。那么剩下的pthread_RWlock_unlock工作代码如下:
首先要先判断当前是否有写的线程等待,这里并不是把优先权给写的线程,而是在有大量的并发读线程下,必须给写的线程点机会,否则它永远都执行不了。这里你可能对pRw->rw_refcount == 0有点疑问。作者的原意可能是这样的:一个写线程在工作中,另一个写线程刚好释放完,准备通知另一个正在写的线程,但这个时候>rw_refcount仍等于-1,通知是无意义的。其实我也是,我认为不会触发这个条件。因为写的线程是确保只有一个执行,而在释放锁的时候已经确保pRw->rw_refcount = 0,当然这会在我代码验证后给出。
好了,现在我们以文字的形式整理下所有的思路。
假设有3个读线程进行并发,那么它们一开始没有检测到rw_refcount <0和rw_nwaitwriters>0,于是他们很欢快的取的读的权限进行并发的读取,假设这个读取的时间很长。。。。这个时候一条写线程进入,它判断rw_refcount不等于0,于是进行阻塞。
在这个时间里,又有3个读线程发生,但是它们检测到了nwaitwriters大于0,也进行阻塞。。终于,最早的3个读线程工作完了,释放完后,通知正在等待的写线程。写线程工作完后,这时候没有其他的写线程进入,所以pthread_cond_broadcast发生了作用,它通知了最后进入的3个读线程。。。
一个大问题,线程被取消掉的死锁
假设有两个线程,读和写,首先保证thread1的先执行
这段代码的工作模式如下:thread1先获取一个读锁,睡眠3秒,thread2阻塞在获取写锁上,thread1取消掉thread2,最后thread1调用unlock函数。这时候阻塞在了unlock里。
为什么?这得了解一个线程的知识,pthread_cond_wait函数发生时,会自动释放掉一个mutex,以运行其他线程进入等待。(如果没有释放这个mutex,那么就没有什么并发等待的意义了,大家都阻塞在最外面的lock),当thread1进行取消时,这个mutex没有释放(或者看成pthread_cond_wait之前的释放mutex没什么效果)。那么在unlock里,也需要对这个mutex进行访问,而造成了死锁。这也是许多多线程里死锁的隐患。具体的解决方案见代码,其实很简单,就是压入一个释放函数,如果线程发生意外直接返回则调用该函数。
其中附件中的代码为自己重新封装后的代码和一个测试代码
编译环境如下
Thread model: posix
gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
正文:
读优先的读写锁策略
假设对于一个共享的数据区来说,读/写都需要,而写的并发又可能比较频繁一点,如果按照普通的mutex思路可能是以下结构:
lock() Write()/Read() Unlock()
对于write来说,对于共享的形式是独占(shared-exclusive);但对于读锁来说对于共享的形式是共享锁(shared lock),即不需要mutex的互斥保护。那么以上的写法中,对于读的情况效率大大的打折。比如一个银行帐户,多个线程可以同时读出某个帐户的收支结余,但是一旦有一个线程需要更新某个给定收支结余,该线程就必须等待所有读出者完成该收支结余的读出,然后只允许该更新线程修改这个收支结余。那么在实现这个策略时,必须遵循以下三个条件
1、 保证并发的读取,不对读取做什么界限的限制,仅仅以一个计数器来表示当前的读取量
2、 有写入线程时,保证写入线程的独立执行
3、 在有写入线程发生,以这个写入线程时间点为界限,之前的等待读线程要执行完,之后的读线程处于等待状态
那么可以设置这么一个结构体`
typedef struct { pthread_mutex_t rw_mutex; /* basic lock on this struct */ pthread_cond_t rw_condreaders; /* for reader threads waiting */ pthread_cond_t rw_condwriters; /* for writer threads waiting */ int rw_magic; /* for error checking */ int rw_nwaitreaders; /* the number waiting */ int rw_nwaitwriters; /* the number waiting */ int rw_refcount; } pthread_RWlock_t; /* *rw_nwaitreaders:当前有读的线程工作时,写的线程阻塞,并且该变量自增 *rw_refcount:表示锁的工作状态,如果是写用-1表示,如果当前无工作状态用0表示,读的 *状态为大于1,其数量代表了当前的读的并发数 */
再设置两个获取读/写锁函数:
int pthread_RWlock_rdlock (pthread_RWlock_t *pRw);
int pthread_RWlock_wrlock (pthread_RWlock_t *pRw);
流程的判断变为如下的工作流程,这里我先把图给出,问题统一放在后面分析
获取读/写锁两个函数大体流程如下:
pthread_RWlock_wrlock函数流程如下:
pthread_RWlock_rdlock函数流程如下:
问题一:先说明一个技巧
这里用了phtread_cond_wait(), 防止一个不断的轮询过程,代码是这样的
while( pRw->rw_refcount != 0 ) { pRw->rw_nwaitwriters++; iResult = pthread_cond_wait(&pRw->rw_condwriters, &pRw->rw_mutex); pRw->rw_nwaitwriters--; if( iResult != 0 ) { break; } }
注意,这里用了个while.在多线程的情况下,有多个cond_wait进行等待,当rw_mutex的锁被释放时,可能被其他线程抢到(即不保证被本身的线程取到),并对rw_refcount进行修改。如果不对rw_refcount进行判断而直接执行,就可能造成数据的不准确。
问题二:获取读锁时对rw_refcount!= 0的判断
这要和读锁的rw_refcount <0和rw_nwaitwriters>0两个条件结合起来进行解释。首先假设当前有多个线程进行工作,这时有个写线程进来,发生阻塞,waitwriters自加。这个时候比这个写线程后来的读线程检测到rw_nwaitwriters>0而发生阻塞,在这之前的读线程仍在工作,这里我们还需要知道pthread_RWlock_unlock的部分工作代码
/* 对读锁的释放为:如果是读锁,refcount减1,信号通知 对写锁的释放为:如果是写锁,refconut直接置0,信号通知 */ if( pRw->rw_refcount > 0 ) { pRw->rw_refcount--; } else if( pRw->rw_refcount == -1 ) { pRw->rw_refcount = 0; }
看吧,把之前对读锁的获取时rw_refcount++和这里的rw_refcount—结合起来,可得知,当读线程都工作完毕时,rw_refcount必然会等于0,这也满足了我们开头提出的三个条件中的最后一条。那么剩下的pthread_RWlock_unlock工作代码如下:
if( pRw->rw_nwaitwriters > 0 ) { /* 防止发送空的signal */ if( pRw->rw_refcount == 0 ) { iResult = pthread_cond_signal(&pRw->rw_condwriters); } } else if( pRw->rw_nwaitreaders > 0 ) { iResult = pthread_cond_broadcast(&pRw->rw_condreaders); }
首先要先判断当前是否有写的线程等待,这里并不是把优先权给写的线程,而是在有大量的并发读线程下,必须给写的线程点机会,否则它永远都执行不了。这里你可能对pRw->rw_refcount == 0有点疑问。作者的原意可能是这样的:一个写线程在工作中,另一个写线程刚好释放完,准备通知另一个正在写的线程,但这个时候>rw_refcount仍等于-1,通知是无意义的。其实我也是,我认为不会触发这个条件。因为写的线程是确保只有一个执行,而在释放锁的时候已经确保pRw->rw_refcount = 0,当然这会在我代码验证后给出。
好了,现在我们以文字的形式整理下所有的思路。
假设有3个读线程进行并发,那么它们一开始没有检测到rw_refcount <0和rw_nwaitwriters>0,于是他们很欢快的取的读的权限进行并发的读取,假设这个读取的时间很长。。。。这个时候一条写线程进入,它判断rw_refcount不等于0,于是进行阻塞。
在这个时间里,又有3个读线程发生,但是它们检测到了nwaitwriters大于0,也进行阻塞。。终于,最早的3个读线程工作完了,释放完后,通知正在等待的写线程。写线程工作完后,这时候没有其他的写线程进入,所以pthread_cond_broadcast发生了作用,它通知了最后进入的3个读线程。。。
一个大问题,线程被取消掉的死锁
假设有两个线程,读和写,首先保证thread1的先执行
void *thread1(void *) { int iResult; CRWLock.pthread_RWlock_rdlock(&rwlock); cout<<"thread 1 got a read lock"<<endl; sleep(3); /*让thread2阻塞在pthread_RWlock_wrlock()*/ pthread_cancel(tid2); sleep(3); iResult = CRWLock.pthread_RWlock_unlock(&rwlock); return 0; } void *thread2(void *) { cout<<"thread 2 trying to obtain a write lock"<<endl; CRWLock.pthread_RWlock_wrlock(&rwlock); cout<<"thread2 got a write lock"<<endl; sleep(1); CRWLock.pthread_RWlock_unlock(&rwlock); return 0; }
这段代码的工作模式如下:thread1先获取一个读锁,睡眠3秒,thread2阻塞在获取写锁上,thread1取消掉thread2,最后thread1调用unlock函数。这时候阻塞在了unlock里。
为什么?这得了解一个线程的知识,pthread_cond_wait函数发生时,会自动释放掉一个mutex,以运行其他线程进入等待。(如果没有释放这个mutex,那么就没有什么并发等待的意义了,大家都阻塞在最外面的lock),当thread1进行取消时,这个mutex没有释放(或者看成pthread_cond_wait之前的释放mutex没什么效果)。那么在unlock里,也需要对这个mutex进行访问,而造成了死锁。这也是许多多线程里死锁的隐患。具体的解决方案见代码,其实很简单,就是压入一个释放函数,如果线程发生意外直接返回则调用该函数。
- myRWlock.rar (3.3 KB)
- 下载次数: 13
发表评论
-
linux内存分配slub的几个疑问
2011-01-13 08:21 1704对于SLUB不熟的同学可以 ... -
开源一个windows下的内存分配器slab,
2010-10-28 20:23 1457模仿linux内核下的slab而写。一些地址页面做了些新的工作 ... -
网络编程中缓冲带来的异步以及需要设置(windows)
2010-08-08 03:50 0此次的疑问主要来自IOCP下的程序编写。 WINDOWS下的 ... -
小议内存池、资源池
2010-08-02 21:08 2198比较简单的一篇文章。本来是有些地方没想明白,想分析一下。结果写 ... -
(00XX系列)抽抽Windows宽字符的棉絮(附日志文件源码)
2009-11-21 17:18 1266爷最近和可 ... -
(00XX系列)摸摸Windows的SEH
2009-11-16 23:13 1669节一:终止处理(Termination Handlers) 节 ... -
配置主机无线网络+虚拟机linux的上网
2009-09-05 11:04 4872环境如下: windows2003主机,虚拟机VM精简版,装有 ... -
碎片:linux vs windows, 内存/硬盘
2009-08-23 13:12 1679找了一堆资料,稍微整 ... -
详解sigaction
2008-12-18 22:24 10608本杂文主要是讲解了下信号和进程的关系。前面主要是一些man式的 ... -
基础:systemV 信号 create send recv rmid
2008-07-26 17:30 1236/* @gcc version 3.2.2 200302 ... -
让您轻松理解execl函数系列 ^_^
2008-06-19 20:03 6884execl函数功能如下:启动一个可执行文件,并且对他进行传送参 ... -
[收集 转载]关于Linux下编写和编译程序的几个问题
2008-04-23 09:09 1921FROM http://www.fmm7.com/jsjc/w ... -
资料存放 linux命令收集和问题记录
2008-03-30 23:03 1469rm -rf name //递归删除 -
Linux基本目录用途
2008-03-24 10:19 2014/bin 该目录中存放Linux的常用命令,在有的版本中是一 ... -
在CentOS 4.4下安装gcc--RPM
2008-03-23 22:28 3397yum install gcc(需要的组件,即你也可以下载以下 ... -
资料存放 yum
2008-03-23 20:47 1103使用 yum 升级和 yum 使用 ... -
资料存放 tar
2008-03-23 20:46 1112tar命令 tar可以为文件和目录创建档案。利用tar,用户可 ... -
初学LINUX:架设一个 VSFTPD服务器系列
2008-03-09 01:33 1606实习的公司需要用到 LINUX,而自己也想深入这方面 公司用的 ... -
虚拟机中设置linux连接网络
2008-03-01 17:21 3862虚拟机:vmware Linux版本:CentOS(版本不会造 ... -
CentOS资料分类
2008-02-08 16:30 1796http://centos.ustc.edu.cn/CentO ...
相关推荐
通信是进程间交换信息的方式,分为共享内存和消息传递两种主要形式。在ORANGE’S中,可能会看到如管道、消息队列、信号量等通信机制的实现。共享内存允许进程直接读写同一块内存区域,而消息传递则是通过发送和接收...
在操作系统中,读者写者问题是描述多个进程对共享资源访问的一种情况。读者可以同时读取数据,而写者必须独占资源来修改数据,以防止写者写入的数据被多个读者同时读取导致数据不一致。课程设计的目标是实现一个能够...
MPI程序通过进程间的通信进行数据交换,实现并行计算。 3. CUDA:NVIDIA公司推出的GPU并行计算框架,专为CUDA兼容的GPU设计,提供了一种直接访问GPU硬件资源的编程方式,适合于高性能计算和图形处理。 四、并行编程...
- 进程间通信(IPC)是多个进程之间交换数据和同步的方法。 - Unix提供了一系列IPC机制,如管道、信号等。 - **6.2 管道和系统调用pipe()** - 管道是一种简单的IPC机制,用于在两个进程之间传递数据。 - `pipe`...
6. **线程通信**:讲述了wait()、notify()和notifyAll()方法以及它们在实现线程间通信中的角色。同时,还讨论了高级的阻塞队列(BlockingQueue)及其在生产者-消费者模型中的应用。 7. **并发设计模式**:介绍了...
8. **并发设计模式**:书中还会介绍一些经典的并发设计模式,如生产者消费者模型、读写锁策略等,这些模式在实际开发中有着广泛的应用。 9. **并发编程最佳实践**:除了理论知识,书中还会分享一些并发编程的最佳...
在Linux内核中,进程管理是核心功能之一。它涉及进程的创建、调度、同步和通信等过程。手册可能会详细介绍`fork()`、`execve()`、`waitpid()`等系统调用,以及进程状态的转换,如运行(Runnable)、睡眠(Sleeping)...
- **读者-写者问题**:介绍如何在多读少写场景下设计高效的并发控制策略。 #### 六、总结 《Introduction to Concurrency Theory》是一本全面介绍并发理论的教材,不仅涵盖了基本的概念和技术,还涉及到了过程代数...
6.3 System V 的进程间通信 6.3.1 公共元素 6.3.2 信号量 6.3.3 消息队列 6.3.4 共享内存 6.3.5 讨论 6.4 Mach IPC 6.4.1 基本概念 6.5 消息 6.5.1 消息的数据结构 6.5.2 消息传递接口 6.6 端口 6.6.1 端口名字空间 ...
- 进程间通信(IPC) - 信号处理机制 2. **线程控制** - 线程的基本概念 - 线程同步机制 - 多线程程序设计技巧 3. **高级文件I/O** - 文件描述符操作 - 高级文件访问方法 - 文件锁定与共享内存 4. **网络...
在pthread库中,主要使用条件变量和互斥锁实现线程间的通信和同步。 六、线程属性 pthread库允许设置线程的属性,如调度策略、栈大小等。`pthread_attr_init()`和`pthread_attr_set*()`函数可以用来初始化和修改...
- **CopyOnWriteArrayList**:写时复制策略,适用于读多写少的场景。 - **ConcurrentHashMap**:线程安全的哈希表实现。 - **ConcurrentLinkedQueue**:基于链表的无界队列。 **线程局部变量:** - **ThreadLocal类...
- `CopyOnWriteArrayList`和`CopyOnWriteArraySet`:讨论了这些线程安全的集合类,适合于读多写少的场景。 4. **原子变量和volatile** - `AtomicInteger`等原子类:阐述了如何使用原子变量进行无锁编程,保证数据...
- **读者-写者问题**:解决多个读线程和一个写线程访问共享资源的问题。 - **屏障同步**:使多个线程同步到达某个点后再继续执行。 #### 五、案例分析与实践 **1. 实例解析** - 通过具体的实例来展示如何使用...
- **通道读/写**:读取或写入数据到特定的通信通道。 - **协议读/写/解码/编码**:操作复杂的协议数据单元。 **2. 内容附件** - **二进制文件/文本文件**:读写不同类型的文件。 **3. 自动化测试** - **断言操作...
17.3 读 写 文 件 .222 17.4 异步文件操作 .227 17.5 小 结 .234 第十八章 高 级 话 题 .235 18.1 注册表编程 .235 18.2 在 C #代码中调用 C++和 VB 编写的组件 .240 18.3 版 本 控 制 .249 18.4 代 ...