转贴:来自 闻怡洋 http://www.vchelp.net/
由于进程/线程间的操作是并行进行的,所以就产生了一个数据的问题同步,我们先看一段代码: int iCounter=0;//全局变量 现在假设有两个线程threadA1和threadA2在同时运行那么运行结束后iCounter的值会是多少,是200吗?不是的,如果我们将Sleep(1000)前的注释去掉后我们会很容易明白这个问题,因为在iCounter的值被正确修改前它可能已经被其他的线程修改了。这个例子是一个将机器代码操作放大的例子,因为在CPU内部也会经历数据读/写的过程,而在线程执行的过程中线程可能被中断而让其他线程执行。变量iCounter在被第一个线程修改后,写回内存前如果它又被第二个线程读取,然后才被第一个线程写回,那么第二个线程读取的其实是错误的数据,这种情况就称为脏读(dirty read)。这个例子同样可以推广到对文件,资源的使用上。 int iCounter=0;//全局变量 { release lock of iCounter 幸运的是OS提供了多种同步对象供我们使用,并且可以替我们管理同步对象的加锁和解锁。我们需要做的就是对每个需要同步使用的资源产生一个同步对象,在使用该资源前申请加锁,在使用完成后解锁。接下来我们介绍一些同步对象: 临界区:临界区是一种最简单的同步对象,它只可以在同一进程内部使用。它的作用是保证只有一个线程可以申请到该对象,例如上面的例子我们就可以使用临界区来进行同步处理。几个相关的API函数为: VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );产生临界区 //全局变量 DWORD threadA(void* pD) } 接下来要讲互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。下面介绍可以用在互斥量上的API函数: 创建互斥量: 你会说为什么没有名称如同EnterMutex,功能如同EnterCriticalSection一样的函数来获得互斥量的使用权呢?的确没有!获取互斥量的使用权需要使用函数: DWORD WaitForSingleObject( 在线程调用WaitForSingleObject后,如果一直无法得到控制权线程讲被挂起,直到超过时间或是获得控制权。 讲到这里我们必须更深入的讲一下WaitForSingleObject函数中的对象(Object)的含义,这里的对象是一个具有信号状态的对象,对象有两种状态:有信号/无信号。而等待的含义就在于等待对象变为有信号的状态,对于互斥量来讲如果正在被使用则为无信号状态,被释放后变为有信号状态。当等待成功后WaitForSingleObject函数会将互斥量置为无信号状态,这样其他的线程就不能获得使用权而需要继续等待。WaitForSingleObject函数还进行排队功能,保证先提出等待请求的线程先获得对象的使用权,下面的代码演示了如何使用互斥量来进行同步,代码的功能还是进行全局变量递增,通过输出结果可以看出,先提出请求的线程先获得了控制权: int iCounter=0; DWORD threadA(void* pD) for(int i=0;i<8;i++) //in main function //创建线程 //关闭句柄 在这里我没有使用全局变量来保存互斥量句柄,这并不是因为不能这样做,而是为演示如何在其他的代码段中通过名字来打开已经创建的互斥量。其实这个例子在逻辑上是有一点错误的,因为iCounter这个变量没有跨进程使用,所以没有必要使用互斥量,只需要使用临界区就可以了。假设有一组进程在同时使用一个文件那么我们可以使用互斥量来保证该文件只同时被一个进程使用(如果只是利用OS的文件存取控制功能则需要添加更多的错误处理代码),此外在调度程序中也可以使用互斥量来对资源的使用进行同步化。 |
|
#4 得分:10回复于: 2003-07-24 11:30:33
现在我们回过头来讲WaitForSingleObject这个函数,从前面的例子中我们看到WaitForSingleObject这个函数将等待一个对象变为有信号状态,那么具有信号状态的对象有哪些呢?下面是一部分:
Mutex DWORD WaitForMultipleObjects( 返回值意义: 前面的例子中的如下代码表示等待三个线程都变为有信号状态,也就是说三个线程都结束。 通过互斥量我们可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,你的老板会要求你根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。对信号灯的操作伪代码大致如下: Semaphore sem=3; dword threadA(void*) 这里信号灯有一个初始值,表示有多少进程/线程可以进入,当信号灯的值大于0时为有信号状态,小于等于0时为无信号状态,所以可以利用WaitForSingleObject进行等待,当WaitForSingleObject等待成功后信号灯的值会被减少1,直到释放时信号灯会被增加1。用于信号灯操作的API函数有下面这些: 创建信号灯: 可以看出来信号灯的使用方式和互斥量的使用方式非常相似,下面的代码使用初始值为2的信号灯来保证只有两个线程可以同时进行数据库调用: DWORD threadA(void* pD) for(int i=0;i<3;i++) //创建线程 //关闭句柄 信号灯有时用来作为计数器使用,一般来讲将其初始值设置为0,先调用ReleaseSemaphore来增加其计数,然后使用WaitForSingleObject来减小其计数,遗憾的是通常我们都不能得到信号灯的当前值,但是可以通过设置WaitForSingleObject的等待时间为0来检查信号灯当前是否为0。 接下来我们讲最后一种同步对象:事件,前面讲的信号灯和互斥量可以保证资源被正常的分配和使用,而事件是用来通知其他进程/线程某件操作已经完成。例如:现在有三个线程:threadA,threadB,threadC,现在要求他们中的部分功能要顺序执行,也就是说threadA执行完一部分后threadB执行,threadB执行完一部分后threadC开始执行。也许你觉得下面的代码可以满足要求: 要求:A1执行完后执行B2然后执行C3,再假设每个任务的执行时间都为1,而且允许并发操作。 dword threadB(void*) dword threadC(void*) 方案二: dword threadB(void*) dword threadC(void*) main() 方案三: dword threadB(void*) dword threadC(void*) main() 可以看出来方案三的执行时间是最短的,当然这个例子有些极端,但我们可以看出事件对象用于通知其他进程/线程某件操作已经完成方面的作用是很大的,而且如果有的任务要在进程尖进行协调采用等待其他进程中线程结束的方式是不可能实现的。此外我也希望通过这个例子讲一点关于分析线程执行效率的方法。 |
|
#5 得分:20回复于: 2003-07-24 11:30:52
一两种方式创建,一种为自动重置,在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。事件相关的API如下:
创建事件对象: 下面的代码演示了自动重置和人工重置事件在使用中的不同效果: DWORD threadA(void* pD) printf("\tthread %d begin\n",iID); DWORD threadB(void* pD) if(WAIT_TIMEOUT == WaitForSingleObject(hCounterIn,10*1000)) //in main function //创建线程 if( (hCounter=OpenEvent(EVENT_ALL_ACCESS,FALSE,"sam sp 44"))==NULL) 从执行结果中我们可以看到在第二次执行时由于使用了自动重置事件threadB中只有一个线程能够等待到threadA中释放的事件对象。 在处理多进程/线程的同步问题时必须要小心避免发生死锁问题,比如说现在有两个互斥量A和B,两个线程tA和tB,他们在执行前都需要得到这两个互斥量,但现在这种情况发生了,tA拥有了互斥量A,tB拥有了互斥量B,但它们同时都在等待拥有另一个互斥量,这时候显然谁也不可能得到自己希望的资源。这种互相拥有对方所拥有的资源而且都在等待对方拥有的资源的情况就称为死锁。关于这个问题更详细的介绍请参考其他参考书。 在MFC中对于各种同步对象都提供了相对应的类 在这些类中封装了上面介绍的对象创建,打开,控制,删除功能。但是如果要使用等待功能则需要使用另外两个类:CSingleLock和CMultiLock。这两个类中封装了WaitForSingleObject和WaitForMultipleObjects函数。如果大家觉的需要可以看看这些类的定义,我想通过上面的介绍可以很容易理解,但是在对象同步问题上我觉得使用API函数比使用MFC类更为直观和方便。 |