使用临界段原理实现优化的进程间同步对象-原理和实现
by jeffrey.richter
vcbear 热情讲解实现自己的同步对象?需要吗?
不需要吗?
...
只是跟你研究一下而已.
算了吧我只是个爱灌水的家伙,很久没有写代码了,闲来无事,灌灌水还不行吗?
1.概述:
在多进程的环境里,需要对线程进行同步.常用的同步对象有临界段(critical section),互斥量(mutex),信号量(semaphore),事件(event)等,除了临界段,都是内核对象。
在同步技术中,临界段(critical section)是最容易掌握的,而且,和通过等待和释放内核态互斥对象实现同步的方式相比,临界段的速度明显胜出.但是临界段有一个缺陷,win32文档已经说明了临界段是不能跨进程的,就是说临界段不能用在多进程间的线程同步,只能用于单个进程内部的线程同步.
因为临界段只是一个很简单的数据结构体,在别的进程的进程空间里是无效的。就算是把它放到一个可以多进程共享的内存映象文件里,也还是无法工作.
有甚么方法可以跨进程的实现线程的高速同步吗?
2.原理和实现
2.1为什么临界段快? 是“真的”快吗?
确实,临界段要比其他的核心态同步对象要快,因为entercriticalsection和leavecriticalsection这两个函数从interlockedxxx系列函数中得到不少好处(下面的代码演示了临界段是如何使用interlockedxxx函数的)。interlockedxxx系列函数完全运行于用户态空间,根本不需要从用户态到核心态
之间的切换。所以,进入和离开一个临界段一般只需要10个左右的cpu执行指令。而当调用waitforsingleobject之流的函数时,因为使用了内核对象,线程被强制的在用户态和核心态之间变换。在x86处理器上,这种变换一般需要600个cpu指令。看到这里面的巨大差距了把。
话说回来,临界段是不是真正的“快”?实际上,临界段只在共享资源没有冲突的时候是快的。当一个线程试图进入正在被另外一个线程拥有的临界段,即发生竞争冲突时,临界段还是等价于一个event核心态对象,一样的需要耗时约600个cpu指令。事实上,因为这样的竞争情况相对一般的运行情况来说是很少的(除非人为),所以在大部分的时间里(没有竞争冲突的时候),临界段的使用根本不牵涉内核同步,所以是高速的,只需要10个cpu的指令。(bear说:明白了吧,纯属玩概率,ms的小花招)
2.3进程边界怎么办?
“临界段等价于一个event核心态对象”是什么意思?
看看临界段结构的定义先
typedef struct _rtl_critical_section {
prtl_critical_section_debug debuginfo;
//
// the following three fields control entering and exiting the critical
// section for the resource
//
long lockcount;
long recursioncount;
handle owningthread; // from the thread's clientid->uniquethread
handle locksemaphore;
dword spincount;
} rtl_critical_section, *prtl_critical_section;
#typedef rtl_critical_section criticl_section
在critical_section 数据结构里,有一个event内核对象的句柄(那个undocument的结构体成员locksemaphore,包含的实际是一个event的句柄, 而不是一个信号量semaphore)。正如我们所知,内核对象是系统全局的,但是该句柄是进程所有的,而不是系统全局的。所以,就算把一个临界段结构直接放到共享的内存映象里,临界段也无法起作用,因为locksemaphore里句柄值只对一个进程有效,对于别的进程是没有意义的。 在一般的进程同步中,进程要使用一个别的进程创建的的event 对象,必须调用openevent或creaetevent函数来得到进程可以使用的句柄值。
critical_section结构里其他的变量是临界段工作所依赖的元素,ms也“警告”程序员不要自己改动该结构体里变量的值。是怎么实现的呢?看下一步.
2.4 coptex,优化的同步对象类
jeffrey richter曾经写过一个自己的临界段,现在,他把他的临界段改良了一下,把它封装成一个coptex类。成员函数tryenter拥有nt4里介绍的函数tryentercriticalsection的功能,这个函数尝试进入临界段,如果失败立刻返回,不会挂起线程,并且支持spin计数.这个功能在nt4/sp3中被initializecriticalsectionandspincount 和setcriticalsectionspincount实现。spin计数在多处理器系统和高竞争冲突情况下是很有用的,在进入waitforxxx核心态之前,临界段根据设定的spin计数进行多次tryenterctriticalsection,然后才进行堵塞。想一下,tryentercriticalsection才使用10个左右的周期,如果在spin计数消耗完之前,冲突消失,临界段对象是空闲的,那么再用10个cpu周期就可以在用户态进入临界段了,不用切换到核心态.
(bear说:为了避免这个"核心态",ms自己也是费劲脑汁呀.看出来了吧,优化的原则:在需要的时候才进入核心态。否则,在用户态进行同步)
以下是coptex代码。
原代码下载figure 2: coptex
optex.h
/******************************************************************************