Horin|贺勤
Email: horin153@msn.com
Blog: http://blog.csdn.net/horin153/
最近看 MSDN 中的一些资料,有感于世界变化真快,从前掌握的锁的知识实在肤浅;于是毫不犹豫地将其拖入回收站,顺便也有了这篇文章。
在这里我不粘贴 MSDN 中的大段叙述,也不复述锁的知识。仅谈新锁的特点:
1、根据 os 版本来决定线程切换方式: Win9x 用 Sleep, WinNT 用 SwitchToThread。
调用 Sleep(0) 时,调用线程虽会放弃剩余时间片,但仍保持 ready 状态。而 SwitchToThread 会促使 os 重新调度线程,并可能使低优先级的线程运行;该接口在 Win9x 系统中不可用。
2、根据 cpu 个数来决定是否 spin: 单 cpu 直接切换线程, 多 cpu 则 spin。
和 InitializeCriticalSectionAndSpinCount 相似,单处理器系统直接切换线程,不进行 spin 处理。
实现代码如下:
// --------------------- code begin -----------------------
class CSpinInterlocked
{
public:
CSpinInterlocked(DWORD processor_num=1, bool win9x=false, int spin_count=128)
: _atomic_mutex(0)
, _spin_count(spin_count)
{
_switch_thread = win9x ? &CSpinInterlocked::switch_thread_9x : /
&CSpinInterlocked::switch_thread_nt;
_lock = (processor_num == 1) ? &CSpinInterlocked::lock_single_processor : /
&CSpinInterlocked::lock_multi_processor;
}
~CSpinInterlocked() {
}
void lock(void) {
(this->*_lock)();
}
void unlock(void) {
::InterlockedExchange(&_atomic_mutex, 0);
}
private:
typedef void (CSpinInterlocked::*pFun)(void);
long volatile _atomic_mutex;
int _spin_count;
pFun _switch_thread;
pFun _lock;
void lock_single_processor(void) {
if (::InterlockedExchange(&_atomic_mutex, 1)) {
// On single-processor systems, yield execution.
(this->*_switch_thread)();
}
}
void lock_multi_processor(void)
{
if (::InterlockedExchange(&_atomic_mutex, 1)) {
// On multi-processor systems, start a spin.
for (int i = 0; ::InterlockedExchange(&_atomic_mutex, 1) && /
i<_spin_count; ++i) {
(this->*_switch_thread)();
}
if (i >= _spin_count) {
while(::InterlockedExchange(&_atomic_mutex, 1)) {
::Sleep(50);
}
}
}
}
void switch_thread_9x(void) {
::Sleep(0); // on Win98
}
//#define _WIN32_WINNT 0x0400
void switch_thread_nt(void) {
::SwitchToThread(); // on WinNT
}
};
// ---------------------- code end ------------------------
对 CSpinInterlocked 进行了简单测试,在 WinXP sp2 + Intel P4 HT 2.8G 上, 直觉上 CriticalSection 比 CSpinInterlocked 效率高。这也许是超线程的影响。
相比 CriticalSection 只能用于进程内的线程间的局限,如果把 CSpinInterlocked 实现在共享内存中,则可以用于进程间。
附测试代码:
// --------------------- code begin -----------------------
CSpinInterlocked g_lock = CSpinInterlocked(get_processor_num(), is_win9x());
// get_processor_num(): 获取处理器数;
// is_win9x(): 判断 os 是否是 Win9x?
int g_count = 0;
int _add_types = 10000;
DWORD WINAPI ThreadFunc( LPVOID lpParam )
{
g_lock.lock();
DWORD _id = reinterpret_cast<DWORD>(lpParam);
cout << "Thread start:" << _id << endl;
for (int i=_add_types; i>0; --i) {
++g_count;
}
cout << "Thread quit:" << _id << endl;
g_lock.unlock();
return EXIT_SUCCESS;
}
int main(void)
{
DWORD dwThreadId = 0, thread_num = 20;
HANDLE hThread = NULL;
for (DWORD i=0; i<thread_num; i++){
hThread = ::CreateThread(
NULL, // default security attributes
0, // use default stack size
ThreadFunc, // thread function
reinterpret_cast<LPVOID>(i), // argument to thread function
0, // use default creation flags
&dwThreadId); // returns the thread identifier
if (hThread == NULL) {
cerr << "CreateThread failed:" << i << endl;
}
}
::Sleep(2000);
// 不加锁时, g_count 一般应该小于 20 * 10000.
// 成功加锁后, g_count 应该等于 20 * 10000.
cout << "g_count=" << g_count << endl;
if (g_count != 20 * 10000) {
cerr << "Thread lock ERROR!" << endl;
}
}
// ---------------------- code end ------------------------
测试用例的一种输出结果如下:
Thread start:0
Thread quit:0
Thread start:8
Thread quit:8
Thread start:4
Thread quit:4
Thread start:5
Thread quit:5
Thread start:16
Thread quit:16
Thread start:2
Thread quit:2
Thread start:1
Thread quit:1
Thread start:19
Thread quit:19
Thread start:13
Thread quit:13
Thread start:14
Thread quit:14
Thread start:15
Thread quit:15
Thread start:12
Thread quit:12
Thread start:7
Thread quit:7
Thread start:6
Thread quit:6
Thread start:11
Thread quit:11
Thread start:18
Thread quit:18
Thread start:3
Thread quit:3
Thread start:10
Thread quit:10
Thread start:9
Thread quit:9
Thread start:17
Thread quit:17
g_count=200000