C++中实现类似Java的“synchronized”
设计思路:通过区域锁和宏定义实现。
本文展示了如何synchronized
在 C++ 中编写与 Java 工作方式类似的语句。这段代码的目标是制作一段如下代码,可在 C++ 中编译和执行:
synchronized(myMutex)
{
//TODO put synchronized code here
}
The Mutex class
下面的一段代码展示了一个具有lock/unlock语义的互斥类(在许多库中很常见):
//mutex class
class Mutex
{
public:
//the default constructor
Mutex()
{
InitializeCriticalSection(&m_criticalSection);
}
//destructor
~Mutex()
{
DeleteCriticalSection(&m_criticalSection);
}
//lock
void lock()
{
EnterCriticalSection(&m_criticalSection);
}
//unlock
void unlock()
{
LeaveCriticalSection(&m_criticalSection);
}
private:
CRITICAL_SECTION m_criticalSection;
};
上面的类没有什么特别的:
- 构造时初始化临界区
- 析构时删除临界区
- 方法
lock()
锁定临界区 - 方法
unlock()
解锁临界区
我们将使用临界区,但任何同步原语都适用。
为了与 C++ 建立的代码实践保持一致,我们需要一个特殊的类来实现 RAII(资源获取即初始化)模式。下面的一段代码展示了这样一个类:
//synchronization controller object
class Lock
{
public:
//the default constructor
Lock(Mutex &mutex) : m_mutex(mutex), m_locked(true)
{
mutex.lock();
}
//the destructor
~Lock()
{
m_mutex.unlock();
}
//report the state of locking when used as a boolean
operator bool () const
{
return m_locked;
}
//unlock
void setUnlock()
{
m_locked = false;
}
private:
Mutex &m_mutex;
bool m_locked;
};
本课注意事项:
- 它在构造时锁定互斥锁,并且
- 它在销毁时解锁互斥锁。
使用上面的类非常简单:
Mutex mutex1;
...
Lock lock1(mutex1);
//synchronized code here
该synchronized
语句可以编码为这样的宏:
#define synchronized(M) for(Lock M##_lock = M; M##_lock; M##_lock.setUnlock())
其中,参数M
是用于lock的互斥变量。
以下代码显示了如何使用同步宏:它协调两个线程,在标准输出中打印字母表。没有同步,输出不正确:
//thread count
int thread_count = 0;
//mutex
Mutex mutex1;
//example thread
DWORD CALLBACK thread_proc(LPVOID params)
{
for(int i = 0; i < 10; ++i)
{
synchronized(mutex1)
{
for(char c = 'A'; c <= 'Z'; ++c)
{
cout << c;
}
cout << endl;
}
}
thread_count--;
return 0;
}
//main
int main()
{
thread_count = 2;
CreateThread(0, 0, thread_proc, 0, 0, 0);
CreateThread(0, 0, thread_proc, 0, 0, 0);
while (thread_count) Sleep(0);
getchar();
return 0;
}
该宏利用for
C++ 语句的性质来执行以下操作(按显示的顺序):
- 初始化部分:定义了一个本地锁变量来锁定给定的互斥体;lock 变量包含一个设置为 true 的内部标志。
- 测试部分:对lock变量进行测试,发现为true:执行循环内的代码。
- 增量部分:锁定变量的内部标志设置为false。
- 测试部分:测试lock变量,发现为false:循环退出。
- 退出部分:锁变量被销毁,解锁互斥锁。
与经典 RAII 相比的优势
使用这种方式对 RAII 进行编码比传统方法有一些优势:
- 它使代码更具可读性,
- 它有助于避免声明锁变量,以及
- 它将要与同步范围同步的代码联系起来。
笔记
该synchronized
宏是异常安全的,因为它在销毁时解锁其互斥锁。
PS:不实用,不再进行优化。