A powerful keyword in Java is calledSynchronizedTo facilitate thread synchronization. Today's whimsical attempt is to simulate a similar one in C ++.
When I recently learned C ++ STL and saw the smart pointer chapter, I was not surprised to use the rich features of language to implement various clever ideas. The most typical method is to use the stack object constructor/destructor to maintain the initialization and release of local resources. According to this clever method, you can write one by yourself to implement local code thread synchronization.
Java synchronized has two forms: function-based and block-based. The former is limited by the C ++ syntax and cannot be implemented. Therefore, try the latter.
The block-level syntax is simple:
synchronized(syncObject) { // code}
Because all Java variables inherit from objects, any variables can be used as locks. This cannot be implemented easily in C ++, so we use a specific type of instance as a synchronization variable.
Let's start with the most classic and simple synchronization class.
struct Lock : CRITICAL_SECTION { Lock() { ::InitializeCriticalSection(this); } ~Lock() { ::DeleteCriticalSection(this); } void Enter() { ::EnterCriticalSection(this); } void Leave() { ::LeaveCriticalSection(this); }};
This is the most common encapsulation for Thread Synchronization in windows. You only need to declare a lock instance and call enter and leave before and after the code to be synchronized.
Since it is so easy to use, why should we continue to improve it? Obviously, this method has a major defect. If you forget to call leave, or return/throw to exit before the call, a deadlock will occur.
Therefore, we need a mechanism similar to auto_ptr to automatically maintain the creation and deletion of stack data. For the moment, call it _ auto_lock.
struct _auto_lock { Lock& _lock; _auto_lock(Lock& lock) : _lock(lock) { _lock.Enter(); } ~_auto_lock() { _lock.Leave(); }};
_ Auto_lock refers to a lock instance for initialization and immediately locks the critical section. When it is destroyed, the lock is released.
With this mechanism, we no longer have to worry about calling. Leave (). You only need to provide a lock object to automatically lock and unlock the current block. No longer need to worry about deadlocks.
Lock mylock; void Test(){ // code1 ... // syn code { _auto_lock x(mylock); } // code2 ...}
After entering the SYN code "{", _ auto_lock is constructed. No matter which method is used to exit "}", The Destructor will be called.
The above code is similar to STL and boost and is common. Using the constructor/destructor of stack objects to maintain local resources is a common skill in C ++.
Our goal is one step closer. The following describes how to use the classic macro definition to create a synchronized syntax sugar and finally implement the Syntax:
Lock mylock; void Test(){ // code1 ... synchronized(mylock) { // sync code } // code2 ...}
Obviously, you need a macro named synchronized and define _ auto_lock in it.
#define synchronized(lock) ..... _auto_lock x(lock) ......
At first glance, this syntax is like a loop, and variables must be defined in the loop, so the structure of for (;) is better.
for(_auto_lock x(mylock); ; )
However, we only need to execute the sync code once, so we need another variable to control the number of times. Because only one type of variables can be declared in for, we need to set another loop outside:
for(int _i=0; _i<1; _i++)for(_auto_lock x(mylock); _i<1; _i++)
The synchronized macro replaces mylock with the above Code, neither violating the syntax nor implementing the same process. Benefiting from the loop syntax,You can even use break in synchronized to jump out of the synchronization block.!
We will sort out the above Code and perform a simple test.
#include <stdio.h>#include <windows.h>#include <process.h>struct Lock : CRITICAL_SECTION { Lock() { ::InitializeCriticalSection(this); } ~Lock() { ::DeleteCriticalSection(this); } void Enter() { ::EnterCriticalSection(this); } void Leave() { ::LeaveCriticalSection(this); }};struct _auto_lock { Lock& _lock; _auto_lock(Lock& lock) : _lock(lock) { _lock.Enter(); } ~_auto_lock() { _lock.Leave(); }};#define synchronized(lock) for(int _i=0; _i<1; _i++)for(_auto_lock lock##_x(lock); _i<1; _i++)// ---------- demo ----------Lock mylock;// ---------- test1 ----------void WaitTest(int id){ printf("No.%d waiting...\n", id); synchronized(mylock) { Sleep(1000); } printf("No.%d done\n", id);}void Test1(){ _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 1); _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 2); _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 3);}// ---------- test2 ----------void ThrowFunc(int id){ printf("No.%d waiting...\n", id); synchronized(mylock) { Sleep(1000); throw "some err"; } printf("No.%d done\n", id);}void ThrowTest(int id){ try { ThrowFunc(id); } catch(...) { printf("%d excepted\n", id); }}void Test2(){ _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 1); _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 2); _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 3);}// ---------- test3 ----------void BreakTest(int id){ printf("No.%d waiting...\n", id); synchronized(mylock) { Sleep(1000); break; Sleep(99999999); } printf("No.%d done\n", id);}void Test3(){ _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 1); _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 2); _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 3);}int main(int argc, char* argv[]){ printf("Wait Test. Press any key to start...\n"); getchar(); Test1(); getchar(); printf("Exception Test. Press any key to start...\n"); getchar(); Test2(); getchar(); printf("Break Test. Press any key to start...\n"); getchar(); Test3(); getchar(); return 0;}
In addition to good-looking syntax sugar, the most important feature is that break can be used in synchronized synchronization blocks to jump out without causing deadlocks. This is not implemented by other methods.