网站的建设步骤包括,赶集网招聘,长沙seo关键词排名,单本小说网站源码1. 多线程的那点儿事#xff08;基础篇#xff09; 多线程编程是现代软件技术中很重要的一个环节。要弄懂多线程#xff0c;这就要牵涉到多进程#xff1f;当然#xff0c;要了解到多进程#xff0c;就要涉及到操作系统。不过大家也不要紧张#xff0c;听我慢慢道来。…1. 多线程的那点儿事基础篇 多线程编程是现代软件技术中很重要的一个环节。要弄懂多线程这就要牵涉到多进程当然要了解到多进程就要涉及到操作系统。不过大家也不要紧张听我慢慢道来。这其中的环节其实并不复杂。 1单CPU下的多线程 在没有出现多核CPU之前我们的计算资源是唯一的。如果系统中有多个任务要处理的话那么就需要按照某种规则依次调度这些任务进行处理。什么规则呢可以是一些简单的调度方法比如说 1按照优先级调度 2按照FIFO调度 3按照时间片调度等等 当然除了CPU资源之外系统中还有一些其他的资源需要共享比如说内存、文件、端口、socket等。既然前面说到系统中的资源是有限的那么获取这些资源的最小单元体是什么呢其实就是进程。 举个例子来说在linux上面每一个享有资源的个体称为task_struct实际上和我们说的进程是一样的。我们可以看看task_structlinux 0.11代码都包括哪些内容 [cpp] view plaincopy struct task_struct { /* these are hardcoded - dont touch */ long state; /* -1 unrunnable, 0 runnable, 0 stopped */ long counter; long priority; long signal; struct sigaction sigaction[32]; long blocked; /* bitmap of masked signals */ /* various fields */ int exit_code; unsigned long start_code,end_code,end_data,brk,start_stack; long pid,father,pgrp,session,leader; unsigned short uid,euid,suid; unsigned short gid,egid,sgid; long alarm; long utime,stime,cutime,cstime,start_time; unsigned short used_math; /* file system info */ int tty; /* -1 if no tty, so it must be signed */ unsigned short umask; struct m_inode * pwd; struct m_inode * root; struct m_inode * executable; unsigned long close_on_exec; struct file * filp[NR_OPEN]; /* ldt for this task 0 - zero 1 - cs 2 - dsss */ struct desc_struct ldt[3]; /* tss for this task */ struct tss_struct tss; }; 每一个task都有自己的pid在系统中资源的分配都是按照pid进行处理的。这也就说明进程确实是资源分配的主体。 这时候可能有朋友会问了既然task_struct是资源分配的主体那为什么又出来thread为什么系统调度的时候是按照thread调度而不是按照进程调度呢原因其实很简单进程之间的数据沟通非常麻烦因为我们之所以把这些进程分开不正是希望它们之间不要相互影响嘛。 假设是两个进程之间数据传输那么需要如果需要对共享数据进行访问需要哪些步骤呢 1创建共享内存 2访问共享内存-系统调用-读取数据 3写入共享内存-系统调用-写入数据 要是写个代码大家可能就更明白了 [cpp] view plaincopy #include unistd.h #include stdio.h int value 10; int main(int argc, char* argv[]) { int pid fork(); if(!pid){ Value 12; return 0; } printf(value %d\n, value); return 1; } 上面的代码是一个创建子进程的代码我们发现打印的value数值还是10。尽管中间创建了子进程修改了value的数值但是我们发现打印下来的数值并没有发生改变这就说明了不同的进程之间内存上是不共享的。 那么如果修改成thread有什么好处呢其实最大的好处就是每个thread除了享受单独cpu调度的机会还能共享每个进程下的所有资源。要是调度的单位是进程那么每个进程只能干一件事情但是进程之间是需要相互交互数据的而进程之间的数据都需要系统调用才能应用这在无形之中就降低了数据的处理效率。 2多核CPU下的多线程 没有出现多核之前我们的CPU实际上是按照某种规则对线程依次进行调度的。在某一个特定的时刻CPU执行的还是某一个特定的线程。然而现在有了多核CPU一切变得不一样了因为在某一时刻很有可能确实是n个任务在n个核上运行。我们可以编写一个简单的open mp测试一下如果还是一个核运行的时间就应该是一样的。 [cpp] view plaincopy #include omp.h #define MAX_VALUE 10000000 double _test(int value) { int index; double result; result 0.0; for(index value 1; index MAX_VALUE; index 2 ) result 1.0 / index; return result; } void test() { int index; int time1; int time2; double value1,value2; double result[2]; time1 0; time2 0; value1 0.0; time1 GetTickCount(); for(index 1; index MAX_VALUE; index ) value1 1.0 / index; time1 GetTickCount() - time1; value2 0.0; memset(result , 0, sizeof(double) * 2); time2 GetTickCount(); #pragma omp parallel for for(index 0; index 2; index) result[index] _test(index); value2 result[0] result[1]; time2 GetTickCount() - time2; printf(time1 %d,time2 %d\n,time1,time2); return; } 3多线程编程
为什么要多线程编程呢这其中的原因很多我们可以举例解决 1有的是为了提高运行的速度比如多核cpu下的多线程 2有的是为了提高资源的利用率比如在网络环境下下载资源时时延常常很高我们可以通过不同的thread从不同的地方获取资源这样可以提高效率 3有的为了提供更好的服务比如说是服务器 4其他需要多线程编程的地方等等 2. 多线程的那点儿事之数据同步 多线程创建其实十分简单在windows系统下面有很多函数可以创建多线程比如说_beginthread。我们就可以利用它为我们编写一段简单的多线程代码 [cpp] view plaincopy #include windows.h #include process.h #include stdio.h unsigned int value 0; void print(void* argv) { while(1){ printf(value %x, value %d\n, value, value); value ; Sleep(1000); } } int main() { _beginthread( print, 0, NULL ); _beginthread( print, 0, NULL); while(1) Sleep(0); return 1; } 注意在VC上面编译的时候需要打开/MD开关。具体操作为【project】-【setting】-【c/c】-Category【Code Generation】-【Use run-time library】-【Debug Multithreaded】即可。 通过上面的示例我们看到作为共享变量的value事实上是可以被所有的线程访问的。这就是线程数据同步的最大优势——方便直接。因为线程之间除了堆栈空间不一样之外代码段和数据段都是在一个空间里面的。所以线程想访问公共数据就可以访问公共数据没有任何的限制。 当然事物都有其两面性。这种对公共资源的访问模式也会导致一些问题。什么问题呢我们看了就知道了。 现在假设有一个池塘我们雇两个人来喂鱼。两个人不停地对池塘里面的鱼进行喂食。我们规定在一个人喂鱼的时候另外一个人不需要再喂鱼否则鱼一次喂两回就要撑死了。为此我们安装了一个牌子作为警示。如果一个人在喂鱼他会把牌子设置为FALSE那么另外一个人看到这个牌子就不会继续喂鱼了。等到这个人喂完后他再把牌子继续设置为TRUE。 如果我们需要把这个故事写成代码那么怎么写呢朋友们试试看 [cpp] view plaincopy while(1){ if( flag true){ flag false; do_give_fish_food(); flag true; } Sleep(0); } 上面的代码看上去没有问题了但是大家看看代码的汇编代码看看是不是存在隐患。因为还会出现两个人同时喂食的情况 [cpp] view plaincopy 23: while(1){ 004010E8 mov eax,1 004010ED test eax,eax 004010EF je do_action56h (00401126) 24: if( flag true){ 004010F1 cmp dword ptr [flag (00433e04)],1 004010F8 jne do_action43h (00401113) 25: flag false; 004010FA mov dword ptr [flag (00433e04)],0 26: do_give_fish_food(); 00401104 call ILT15(do_give_fish_food) (00401014) 27: flag true; 00401109 mov dword ptr [flag (00433e04)],1 28: } 29: 30: Sleep(0); 00401113 mov esi,esp 00401115 push 0 00401117 call dword ptr [__imp__Sleep4 (004361c4)] 0040111D cmp esi,esp 0040111F call __chkesp (004011e0) 31: } 00401124 jmp do_action18h (004010e8) 32: } 我们此时假设有两个线程a和b在不停地进行判断和喂食操作。设置当前flag true此时线程a执行到004010F8处时判断鱼还没有喂食正准备执行指令004010F8但是还没有来得及对falg进行设置此时出现了线程调度。线程b运行到004010F8时发现当前没有人喂食所以执行喂食操作。等到b线程喂食结束运行到00401113的时候此时又出现了调度。线程a有继续运行因为之前已经判断了当前还没有喂食所以线程a继续进行了喂食了操作。所以可怜的鱼这一次就连续经历了两次喂食操作估计有一部分鱼要撑死了。 当然鱼在这里之所以会出现撑死的情况主要是因为line 24和line 25之间出现了系统调度。所以我们在编写程序的时候必须有一个牢固的思想意识如果缺少必须要的手段程序可以任何时刻任何地点被调度那此时公共数据的计算就会出现错误。 那么有没有方法避免这种情况的发生呢当然有。朋友们可以继续关注下面的博客。 3. 多线程的那点事儿之数据互斥 在多线程存在的环境中除了堆栈中的临时数据之外所有的数据都是共享的。如果我们需要线程之间正确地运行那么务必需要保证公共数据的执行和计算是正确的。简单一点说就是保证数据在执行的时候必须是互斥的。否则如果两个或者多个线程在同一时刻对数据进行了操作那么后果是不可想象的。 也许有的朋友会说不光数据需要保护代码也需要保护。提出这个观点的朋友只看到了数据访问互斥的表象。在程序的运行空间里面什么最重要的呢代码吗当然不是。代码只是为了数据的访问存在的。数据才是我们一切工作的出发点和落脚点。 那么有什么办法可以保证在某一时刻只有一个线程对数据进行操作呢四个基本方法 1关中断 2数学互斥方法 3操作系统提供的互斥方法 4cpu原子操作 为了让大家可以对这四种方法有详细的认识我们可以进行详细的介绍。 1关中断 要让数据在某一时刻只被一个线程访问方法之一就是停止线程调度就可以了。那么怎样停止线程调度呢那么关掉时钟中断就可以了啊。在X86里面的确存在这样的两个指令 [cpp] view plaincopy #include stdio.h int main() { __asm{ cli sti } return 1; } 其中cli是关中断sti是开中断。这段代码没有什么问题可以编过当然也可以生成执行文件。但是在执行的时候会出现一个异常告警Unhandled exception in test.exe: 0xC0000096: Privileged Instruction。告警已经说的很清楚了这是一个特权指令。只有系统或者内核本身才可以使用这个指令。 不过大家也可以想象一下。因为平常我们编写的程序都是应用级别的程序要是每个程序都是用这些代码那不乱了套了。比如说你不小心安装一个低质量的软件说不定什么时候把你的中断关了这样你的网络就断了你的输入就没有回应了你的音乐什么都没有了这样的环境你受的了吗应用层的软件是千差万别的软件的水平也是参差不齐的所以系统不可能相信任何一个私有软件它相信的只是它自己。 2数学方法 假设有两个线程a、b正要对一个共享数据进行访问那么怎么做到他们之间的互斥的呢其实我们可以这么做 [cpp] view plaincopy unsigned int flag[2] {0}; unsigned int turn 0; void process(unsigned int index) { flag[index] 1; turn index; while(flag[1 - index] (turn index)); do_something(); flag[index] 0; } 其实学过操作系统的朋友都知道上面的算法其实就是Peterson算法可惜它只能用于两个线程的数据互斥。当然这个算法还可以推广到更多线程之间的互斥那就是bakery算法。但是数学算法有两个缺点 a占有空间多两个线程就要flag占两个单位空间那么n个线程就要n个flag空间 b代码编写复杂考虑的情况比较复杂 3系统提供的互斥算法 系统提供的互斥算法其实是我们平时开发中用的最多的互斥工具。就拿windows来说关于互斥的工具就有临界区、互斥量、信号量等等。这类算法有一个特点那就是都是依据系统提高的互斥资源那么系统又是怎么完成这些功能的呢其实也不难。 系统加锁过程 [cpp] view plaincopy void Lock(HANDLE hLock) { __asm {cli}; while(1){ if(/* 锁可用*/){ /* 设定标志表明当前锁已被占用 */ __asm {sti}; return; } __asm{sti}; schedule(); __asm{cli}; } } 系统解锁过程 [cpp] view plaincopy void UnLock(HANDLE hLock) { __asm {cli}; /* 设定标志 当前锁可用 */ __asm{sti}; } 上面其实讨论的就是一种最简单的系统锁情况。中间没有涉及到就绪线程的压入和弹出过程没有涉及到资源个数的问题所以不是很复杂。朋友们仔细看看应该都可以明白代码表达的是什么意思。 4CPU的原子操作 因为在多线程操作当中有很大一部分是比较、自增、自减等简单操作。因为需要互斥的代码很少所以使用互斥量、信号量并不合算。因此CPU厂商为了开发的方便把一些常用的指令设计成了原子指令在windows上面也被称为原子锁常用的原子操作函数有 [cpp] view plaincopy InterLockedAdd InterLockedExchange InterLockedCompareExchange InterLockedIncrement InterLockedDecrement InterLockedAnd InterLockedOr 4.
多线程的那点儿事之自旋锁 自旋锁是SMP中经常使用到的一个锁。所谓的smp就是对称多处理器的意思。在工业用的pcb板上面特别是服务器上面一个pcb板有多个cpu是很正常的事情。这些cpu相互之间是独立运行的每一个cpu均有自己的调度队列。然而这些cpu在内存空间上是共享的。举个例子说假设有一个数据value 10那么这个数据可以被所有的cpu访问。这就是共享内存的本质意义。我们可以看一段Linux 下的的自旋锁代码kernel 2.6.23asm-i386/spinlock.h就可有清晰的认识了 [cpp] view plaincopy static inline void __raw_spin_lock(raw_spinlock_t *lock) { asm volatile(\n1:\t LOCK_PREFIX ; decb %0\n\t jns 3f\n 2:\t rep;nop\n\t cmpb $0,%0\n\t jle 2b\n\t jmp 1b\n 3:\n\t : m (lock-slock) : : memory); } 上面这段代码是怎么做到自旋锁的呢我们可以一句一句看看line 4: 对lock-slock自减这个操作是互斥的LOCK_PREFIX保证了此刻只能有一个CPU访问内存line 5: 判断lock-slock是否为非负数如果是跳转到3即获得自旋锁line 6: 位置符line 7: lock-slock此时为负数说明已经被其他cpu抢占了cpu休息一会相当于pause指令line 8: 继续将lock-slock和0比较line 9: 判断lock-slock是否小于等于0如果判断为真跳转到2继续休息line 10: 此时lock-slock已经大于0可以继续尝试抢占了跳转到1line 11: 位置符 上面的操作除了第4句是cpu互斥操作其他都不是。所以我们发现在cpu之间寻求互斥访问的时候在某一时刻只有一个内存访问权限。所以如果其他的cpu之间没有获得访问权限就会不断地查看当前是否可以再次申请自旋锁了。这个过程中间不会停歇除非获得访问的权限为止。总结1在smp上自旋锁是多cpu互斥访问的基础2因为自旋锁是自旋等待的所以处于临界区的代码应尽可能短3上面的LOCK_PREFIX在x86下面其实就是“lock”gcc下可以编过朋友们可以自己试试5.
多线程的那点儿事之windows锁 在windows系统中系统本身为我们提供了很多锁。通过这些锁的使用一方面可以加强我们对锁的认识另外一方面可以提高代码的性能和健壮性。常用的锁以下四种临界区互斥量信号量event。1临界区 临界区是最简单的一种锁。基本的临界区操作有 [cpp] view plaincopy InitializeCriticalSection EnterCriticalSection LeaveCriticalSection DeleteCriticalSection 如果想要对数据进行互斥操作的话也很简单这样做就可以了[cpp] view plaincopy EnterCriticalSection(/*...*/) do_something(); LeaveCriticalSection(/*...*/) 2互斥锁互斥锁也是一种锁。和临界区不同的是它可以被不同进程使用因为它有名字。同时获取锁和释放锁的线程必须是同一个线程。常用的互斥锁操作有[cpp] view plaincopy CreateMutex OpenMutex ReleaseMutex 那么怎么用互斥锁进行数据的访问呢其实不难。[cpp] view plaincopy WaitForSingleObject(/*...*/); do_something(); ReleaseMutex(/*...*/); 3信号量信号量是使用的最多的一种锁结果也是最方便的一种锁。围绕着信号量人们提出了很多数据互斥访问的方案pv操作就是其中的一种。如果说互斥锁只能对单个资源进行保护那么信号量可以对多个资源进行保护。同时信号量在解锁的时候可以被另外一个thread进行解锁操作。目前常用的信号量操作有[cpp] view plaincopy CreateSemaphore OpenSemaphore ReleaseSemaphore 信号量的使用和互斥锁差不多。关键是信号量在初始化的时候需要明确当前资源的数量和信号量的初始状态是什么 [cpp] view plaincopy WaitForSingleObject(/*...*/); do_something(); ReleaseSemaphore(/*...*/); 4event对象event对象是windows下面很有趣的一种锁结果。从某种意义上说它和互斥锁很相近但是又不一样。因为在thread获得锁的使用权之前常常需要main线程调用SetEvent设置一把才可以。关键是在thread结束之前我们也不清楚当前thread获得event之后执行到哪了。所以使用起来要特别小心。常用的event操作有[cpp] view plaincopy CreateEvent OpenEvent PulseEvent ResetEvent SetEvent 我们对event的使用习惯于分成main thread和normal thread使用。main thread负责event的设置和操作而normal thread负责event的等待操作。在CreateEvent的时候要务必考虑清楚event的初始状态和基本属性。对于main thread应该这么做[cpp] view plaincopy CreateEvent(/*...*/); SetEvent(/*...*/); WaitForMultiObjects(hThread, /*...*/); CloseHandle(/*...*/); 对于normal thread来说操作比较简单 [cpp] view plaincopy while(1){ WaitForSingleObject(/*...*/); /*...*/ } 总结1关于
临界区、
互斥区、
信号量、
event在msdn上均有示例代码2一般来说使用频率上信号量 互斥区 临界区 事件对象3信号量可以实现其他三种锁的功能学习上应有所侧重4纸上得来终觉浅多实践才能掌握它们之间的区别 6.
多线程的那点儿事之C锁 编写程序不容易编写多线程的程序更不容易。相信编写过多线程的程序都应该有这样的一个痛苦过程什么样的情况呢朋友们应该看一下代码就明白了[cpp] view plaincopy void data_process() { EnterCriticalSection(); if(/* error happens */) { LeaveCriticalSection(); return; } if(/* other error happens */) { return; } LeaveCriticalSection(); } 上面的代码说明了一种情形。这种多线程的互斥情况在代码编写过程中是经常遇到的。所以每次对共享数据进行操作时都需要对数据进行EnterCriticalSection和LeaveCriticalSection的操作。但是这中间也不是一帆风顺的。很有可能你会遇到各种各样的错误。那么这时候你的程序就需要跳出去了。可能一开始遇到error的时候你还记得需要退出临界区。但是如果错误多了你未必记得还有这个操作了。这一错就完了别的线程就没有机会获取这个锁了。那么有没有可能利用C的特性自动处理这种情况呢还真有。我们看看下面这个代码[cpp] view plaincopy class CLock { CRITICAL_SECTION cs; public: CLock(CRITICAL_SECTION lock):cs(lock){ EnterCriticalSection(cs); } ~CLock() { LeaveCriticalSection(cs); } } class Process { CRITICAL_SECTION cs; /* other data */ public: Process(){ InitializeCriticalSection(cs); } ~Process() {DeleteCriticalSection(cs);} void data_process(){ CLock lock(cs); if(/* error happens */){ return; } return; } } C的一个重要特点就是不管函数什么时候退出系统都会自动调用类的析构函数。在Process类的data_process函数中函数在开始就创建了一个CLock类。那么在创建这个类的时候其实就开始了临界区的pk。那么一旦进入到临界区当中在error中能不能及时退出临界区呢此时c析构函数的优势出现了。因为不管错误什么时候出现在函数退出之前系统都会帮我们善后。什么善后呢就是系统会调用CLock的析构函数也就是退出临界区。这样我们的目的就达到了。其实这就是一个c的trick。7.
多线程的那点儿事之原子锁 原子锁是多线程编程中的一个特色。然而在平时的软件编写中原子锁的使用并不是很多。这其中原因很多我想主要有两个方面。第一关于原子锁这方面的内容介绍的比较少第二人们在编程上面习惯于已有的方案如果没有特别的需求不过贸然修改已存在的代码。毕竟对很多人来说不求有功但求无过。保持当前代码的稳定性还是很重要的。 其实早在《
多线程数据互斥》这篇博客中我们就已经介绍过原子锁。本篇博客主要讨论的就是原子锁怎么使用。中间的一些用法只是我个人的一些经验希望能够抛砖引玉多听听大家的想法。1查找函数中原子锁 在一些函数当中有的时候我们需要对满足某种特性的数据进行查找。在传统的单核CPU上优化的空间比较有限。但是现在多核CPU已经成了主流配置。所以我们完全可以把这些查找工作分成几个子函数分在几个核上面并行运算。但是这中间就会涉及到一个问题那就是对公共数据的访问。传统的访问方式应该是这样的 [cpp] view plaincopy unsigned int count 0; int find_data_process() { if(/* data meets our standards */){ EnterCriticalSection(cs); count ; LeaveCriticalSection(cs); } } 我们看到代码中间使用到了锁那么势必会涉及到系统调用和函数调度。所以在执行效率上会大打折扣。那么如果使用原子锁呢[cpp] view plaincopy unsigned int count 0; int find_data_process() { if(/* data meets our standards */){ InterLockedIncrement(count); } } 有兴趣的朋友可以做这样一道题目查看00xFFFFFFFF上有多少数可以被3整除大家也可以验证一下用原子锁代替临界区之后代码的效率究竟可以提高多少。关于多核多线程的编程朋友们可以参考《多线程基础篇》这篇博客。 2代码段中的原子锁 上面的范例只是介绍了统计功能中的原子锁。那么怎么用原子锁代替传统的系统锁呢比如说假设原来的数据访问是这样的 [cpp] view plaincopy void data_process() { EnterCriticalSection(cs); do_something(); LeaveCriticalSection(cs); } 如果改成原子锁呢会是什么样的呢[cpp] view plaincopy unsigned int lock 0; void data_process() { while(1 InterLockedCompareExchange(lock, 1, 0)); do_something(); lock 0; } 这里用原子锁代替普通的系统锁完成的功能其实是一样的。那么这中间有什么区别呢其实关键要看do_something要执行多久。打个比方来说现在我们去买包子但是买包子的人很多。那怎么办呢有两个选择如果卖包子的人手脚麻利服务一个顾客只要10秒钟,那么即使前面排队的有50个人我们只要等7、8分钟就可以这点等的时间还是值得的但是如果不幸这个卖包子的老板服务一个顾客要1分钟那就悲催了假使前面有50个人那我们就要等50多分钟了。50分钟对我们来说可是不短的一个时间我们完全可以利用这个时间去买点水果交交水电费什么的过了这个时间点再来买包子也不迟。 和上面的例子一样忙等的方法就是原子锁过一会再来的方法就是哪个传统的系统锁。用哪个就看这个do_something的时间值不值得我们等待了。 8.
多线程的那点儿事之读写锁 在编写多线程的时候有一种情况是十分常见的。那就是有些公共数据修改的机会比较少。相比较改写它们读的机会反而高的多。通常而言在读的过程中往往伴随着查找的操作中间耗时很长。给这种代码段加锁会极大地降低我们程序的效率。那么有没有一种方法可以专门处理这种多读少写的情况呢有那就是读写锁。1首先我们定义一下基本的数据结构。 [cpp] view plaincopy typedef struct _RWLock { int count; int state; HANDLE hRead; HANDLE hWrite; }RWLock; 同时为了判断当前的锁是处于读状态还是写状态我们要定义一个枚举量[cpp] view plaincopy typedef enum { STATE_EMPTY 0, STATE_READ, STATE_WRITE }; 2初始化数据结构 [cpp] view plaincopy RWLock* create_read_write_lock(HANDLE hRead, HANDLE hWrite) { RWLock* pRwLock NULL; assert(NULL ! hRead NULL ! hWrite); pRwLock (RWLock*)malloc(sizeof(RWLock)); pRwLock-hRead hRead; pRwLock-hWrite hWrite; pRwLock-count 0; pRwLock-state STATE_EMPTY; return pRwLock; } 3获取读锁 [cpp] view plaincopy void read_lock(RWLock* pRwLock) { assert(NULL ! pRwLock); WaitForSingleObject(pRwLock-hRead, INFINITE); pRwLock-counnt ; if(1 pRwLock-count){ WaitForSingleObject(pRwLock-hWrite, INFINITE); pRwLock-state STATE_READ; } ReleaseMutex(pRwLock-hRead); } 4获取写锁[cpp] view plaincopy void write_lock(RWLock* pRwLock) { assert(NULL ! pRwLock); WaitForSingleObject(pRwLock-hWrite, INFINITE); pRwLock-state STATE_WRITE; } 5释放读写锁[cpp] view plaincopy void read_write_unlock(RWLock* pRwLock) { assert(NULL ! pRwLock); if(STATE_READ pRwLock-state){ WaitForSingleObject(pRwLock-hRead, INFINITE); pRwLock-count --; if(0 pRwLock-count){ pRwLock-state STATE_EMPTY; ReleaseMutex(pRwLock-hWrite); } ReleaseMutex(pRwLock-hRead); }else{ pRwLock-state STATE_EMPTY; ReleaseMutex(pRwLock-hWrite); } return; } 文章总结1读写锁的优势只有在多读少写、代码段运行时间长这两个条件下才会效率达到最大化2任何公共数据的修改都必须在锁里面完成3读写锁有自己的应用场所选择合适的应用环境十分重要4编写读写锁很容易出错朋友们应该多加练习5读锁和写锁一定要分开使用否则达不到效果。