How to synchronize windows threads

Source: Internet
Author: User

Summary:

For multi-threaded programming, a very important problem is to solve the problem of data competition caused by data sharing, through a certain number of Thread Synchronization Methods to avoid data competition. In Win32 multithreading, the synchronization methods include:User-mode Synchronization: Interlock, criticalsection, srwlock, andKernel state synchronization mode: Event, semaphore, mutex, etc.

The main purpose of this article is to collect these synchronous methods and the meanings and usage of the APIS, which may not involve more in-depth questions, such as when to use better methods, why, and why, it will be accumulated and recorded separately in the future.

I. Data competition example

Before analyzing the synchronization method, let's give an example of the Data competition to be solved:

# Include "stdafx. H "# include <stdio. h> # include <windows. h> # include <process. h> long G = 0; # define thread_count10 // Number of threads # define access_times0000000 // The number of times shared variables are accessed and increased, increase the possibility of data competition void _ cdecl threadproc (void * para) {printf ("sub thread started \ n"); For (INT I = 0; I <access_times; I ++) G = G + 1; printf ("sub thread finished \ n"); _ endthread (); // can be omitted, which is implicitly called .} Int main (INT argc, char * argv []) {handle hthread [thread_count]; for (INT I = 0; I <thread_count; I ++) hthread [I] = (handle) _ beginthread (threadproc, 0, null); For (INT I = 0; I <thread_count; I ++) waitforsingleobject (hthread [I], infinite); // check the result if (G = access_times * thread_count) printf ("correct result! \ N "); else printf (" error result! \ N ");}

If the program outputs an error result, it indicates that data competition has occurred.

Ii. User State Synchronization

Note that the conversion from user State to kernel state requires a certain amount of overhead, so if you can perform synchronization control in user State, try to select the user State mode.

(1) interlocked atomic operation

Interlocked is a series of methods that provide atomic operation implementation. Atomic operations are easy to understand and simple synchronization can be achieved by directly calling the corresponding API. However, this method is obviously only applicable to simple and predefined operation methods, such as auto increment, auto increment, addition and so on, specific methods, can refer to msdn (http://msdn.microsoft.com/zh-cn/site/ms684122)

For the above data competition example, it is clear that auto-increment atomic operations can be used to solve the problem. For auto-increment operations, Ms provides interlockedincrement () and interlockedincrement64 () to complete atomic operations, operate 32-bit and 64-bit integers (Long, long) respectively. The parameter is the address of the variable to be auto-incrementing. You only need to modify "G = G + 1" in the above Code:

InterlockedIncrement(&g); // g = g + 1;

To solve the data competition problem.

(2) critical section of the critical section

Baidu Baike: whether it is a critical hardware resource or a critical software resource, multiple processes must access it with mutual exclusion. The code used to access critical resources in each process is called a critical section (a critical resource is a shared resource that can only be used by one process at a time ). Only one process can enter the critical section at a time. Other processes are not allowed to enter the critical section. Whether it is a hardware critical resource or a software critical resource, multiple processes must access it with mutual exclusion.

Functions related to the critical section include:

Entercriticalsection leavecriticalsectioninitializecriticalsectiondeletdeletecriticalsectiontryentercriticalsection

Entercriticalsection and leavecriticalsection are a pair of operations, indicating to enter and exit the critical section, and control the access to a piece of code synchronously, that is, the code called between the two functions, each time only one thread is allowed to execute.

Initializecriticalsection and deletecriticalsection are also a pair of operations, namely initializing and deleting the critical section (variable). When using the critical section, you need to define a variable in the critical section to represent a resource in the critical section. Therefore, a program can use multiple critical zone variables to protect different code segments. When you call entercriticalsection, if the critical section is occupied by other threads, the current thread will wait for resources and know that other threads exit the critical section. The tryentercriticalsection function is used to try to enter the critical section. If the critical section cannot be entered (the critical section is occupied), false is returned and the thread is not blocked.

The following code uses the critical section to solve the above problem:

# Include "stdafx. H "# include <stdio. h> # include <windows. h> # include <process. h> long G = 0; critical_section g_cs; // defines the critical section # define thread_count10 // Number of threads # define access_times0000000 // The number of times shared variables are accessed and increased, increase the possibility of data competition void _ cdecl threadproc (void * para) {printf ("sub thread started \ n"); For (INT I = 0; I <access_times; I ++) {entercriticalsection (& g_cs); // enter the critical section G = G + 1; leavecriticalsection (& g_cs); // exit the critical section} printf ("Su B thread finished \ n "); _ endthread (); // can be omitted, which is called by implicit .} Int main (INT argc, char * argv []) {initializecriticalsection (& g_cs); // initialize the handle hthread [thread_count] in the critical section; For (INT I = 0; I <thread_count; I ++) hthread [I] = (handle) _ beginthread (threadproc, 0, null); For (INT I = 0; I <thread_count; I ++) waitforsingleobject (hthread [I], infinite); deletecriticalsection (& g_cs); // Delete the critical section // check the result if (G = access_times * thread_count) printf ("correct result! \ N "); else printf (" error result! \ N ");}

It can be seen that the critical section can protect a code block, which is more flexible than atomic operations.

(3) srwlock

About read/write lock: http://baike.baidu.com/view/2214179.htm (Baidu encyclopedia)

The read/write lock is actually a special spin lock. It divides visitors to shared resources into readers and writers, and readers only read and access shared resources, the writer needs to write the shared resources. Compared with the spin lock, this lock can improve concurrency because in a multi-processor system, it allows multiple readers to access Shared resources at the same time, the maximum number of possible readers is the actual number of logical CPUs. The writer is exclusive. A read/write lock can only have one or more writers (related to the number of CPUs), but not both readers and writers.

Note: In Win32, spin locks are defined by the kernel and can only be used in the kernel state. Therefore, General programs will not be used and certain requirements are required. Srwlock is a user-mode read/write lock.

In terms of usage, srwlock is very similar to the use of the critical section. The main difference between a read/write lock and a critical section is that the access to shared resources by the read/write lock distinguishes between the reader and the writer. Therefore, srwlock has more methods than the critical section.

Srwlock-related functions include: (For details, refer to http://msdn.microsoft.com/zh-cn/library/aa904937.aspxto introduce all the APIS related to read/write locks)

Acquiresrwlockshared
Acquiresrwlockexclusive
Releasesrwlockshared
Releasesrwlockexclusive
Initializesrwlock
Tryacquiresrwlockexclusive
Tryacquiresrwlockshared
Sleepconditionvariablesrw

Acquiresrwlockshared and acquiresrwlockexclusive indicate obtaining the read lock and write lock (shared lock and exclusive lock ). Releasesrwlockshared and releasesrwlockexclusive indicate releasing the Read and Write locks. Like the critical section, initializesrwlock is initialized. However, srwlock does not provide a method to delete the read/write lock and does not need to be deleted. Tryacquiresrwlockexclusive and tryacquiresrwlockshared are also used to obtain the Read and Write locks in a non-blocking manner. If they fail, false is returned. Sleepconditionvariablesrw is described below. It should be noted that tryacquiresrwlockexclusive and tryacquiresrwlockshared are supported by win7. For details, refer to msdn.

The following is the problem of using the read/write lock to solve the above data competition: (Note: Only the write lock needs to be obtained here. More programs may need to use both the Read and Write locks)

# Include "stdafx. H "# include <stdio. h> # include <windows. h> # include <process. h> long G = 0; srwlock g_srw; // define the read/write lock # define thread_count10 // Number of threads # define access_times0000000 // increase the number of times shared variables are accessed, increase the possibility of data competition void _ cdecl threadproc (void * para) {printf ("sub thread started \ n"); For (INT I = 0; I <access_times; I ++) {acquiresrwlockexclusive (& g_srw); // obtain the write lock G = G + 1; releasesrwlockexclusive (& g_srw); // release the write lock} printf ("sub Thread finished \ n "); _ endthread (); // can be omitted, which is implicitly called .} Int main (INT argc, char * argv []) {initializesrwlock (& g_srw); // initialize the read/write Lock handle hthread [thread_count]; for (INT I = 0; I <thread_count; I ++) hthread [I] = (handle) _ beginthread (threadproc, 0, null); For (INT I = 0; I <thread_count; I ++) waitforsingleobject (hthread [I], infinite); // check the result if (G = access_times * thread_count) printf ("correct result! \ N "); else printf (" error result! \ N ");}

Applicable situations of read/write locks: The read/write locks are suitable for situations where the number of reads to the data structure is much higher than the number of writes. For more information, see the Introduction to the features of read/write locks.

(4) condition Variable Condition variable

Msdn: http://msdn.microsoft.com/zh-cn/site/ms682052

Conditional variables are available in Linux at the beginning, and Windows platform supports conditional variables only from Vista (So XP does not support them ).

About the interpretation of the Conditional Variable itself, refer to Baidu Encyclopedia (http://baike.baidu.com/view/4025952.htm), of course, Baidu encyclopedia is said in Linux, but the concept itself is actually similar. As follows:

Conditional variables are a mechanism for synchronizing global variables shared between threads. They mainly include two actions: one thread waits"Conditional variables are suspended when the conditions are true.; Another threadMake "condition true" (give the condition true signal). To prevent competition,The use of condition variables is always combined with a mutex lock.. In fact,In Windows, conditional variables are always used in combination with read/write locks or critical sections.(Because there is no critical section in Linux, only mutex locks are mentioned here ).

Functions related to conditional variables include:

Condition Variable Function
Initializeconditionvariable
Sleepconditionvariablecs
Sleepconditionvariablesrw
Wakeallconditionvariable
Wakeconditionvariable

For detailed instructions, refer to msdn. As it is difficult to combine conditional variables with data competition examples, we will not give an example here, and it cannot be synchronized independently, therefore, it is not necessary to use it as a synchronization method here. For more information about how to use and use conditional variables, see other content.

Iii. kernel state Synchronization

All of the above are user-state Synchronization Methods. Win32 multithreading also provides some kernel-state Synchronization Methods. In terms of performance, the kernel state synchronization mode is lower than the user State, because the conversion from the user State to the kernel state is overhead. However, the advantage of kernel mode is thatCross-Process SynchronizationSo it is not only a thread synchronization method, but also a process synchronization method.

(1) kernel objects and statuses

Before learning about kernel state synchronization, you must first understand two important functions: waitforsingleobject and waitformultipleobjects.

1. Kernel Object

The kernel object is only a memory block allocated by the kernel and can only be accessed by the kernel. The memory block is a data structure, and its members are responsible for maintaining various information about the object.Some data members (such as security descriptors and counts) are the same in all object types,Most data members belong to specific object types. For example, a process object has a process I D, a basic priority, and an exit code, while a file object has a byte displacement, a sharing mode, and an open mode.

Refer:

Http://www.cnblogs.com/fangyukuan/archive/2010/08/31/1813117.html

Http:// I .mtime.com/MyFighting/blog/1793762/

In short, the kernel state synchronization objects mentioned here are all kernel objects, including process objects and thread objects. You also need to know that the kernel object is created using the corresponding creation function, and the returned result is a handle, that is, a handle object.

2. kernel synchronization object

In Windows NT kernel objects, five kernel synchronization objects are provided: event, semaphore, mutex), timer (timer), thread (thread ).

For kernel synchronization objects, see: http://hi.baidu.com/klksys/blog/item/2c470ad25808cdd6a9ec9aaa.html

3. Kernel Object status

At any time, any object is in either of the two States: the signal state or the non-signal state (refer to the above Link Description and do not find the official confirmation of this sentence, but at least for Kernel synchronization objects, all objects should be in these two States ). Sometimes, these two states are called the signaled state and nonsignaled state, or the notification state and the unnotified state.

(Reference http://st251256589.blog.163.com/blog/static/16487644920111244054511 ).

Now, we can discuss waitforsingleobject. The waitforsingleobject parameter is a kernel object handle. Its function is: waits until the specified object is in the signaled state or the time-out interval elapses. That is, waiting for the specified object to be In the trusted state or timeout indicates that if the object is in the non-trusted state when waitforsingleobject is executed, the current thread is in the blocking state. Of course, waitformultipleobjects is used to wait for multiple States.

Note: waitforsingleobject is caused by side effects on some kernel objects. For example, for semaphores, waiting for success will reduce the semaphores by 1.

Refer to msdn: Workshop:

Change Notification
Console input
Event
Memory resource notification
Mutex
Process
Semaphore
Thread

Waitable Timer

The bold kernel objects are encountered in multi-thread programming (three kernel state synchronization objects and one thread object ). After understanding the signaled state and nonsignaled state, the following three kernel state synchronization modes are easy to understand.

(2) Event Events

Msdn: http://msdn.microsoft.com/zh-cn/site/ms682655)

There are two types of event kernel objects: manually reset events and automatically reset events.

When a manual reset event is notified, all threads waiting for the event become schedulable threads; it does not successfully wait for side effects.
When an automatically reset event is notified, only one thread in the thread waiting for the event changes to a schedulable thread. The side effect of waiting for success is that the object is automatically reset to the status of not notified.

The event kernel object is created through createevent. It can be in the notification or not notified status initially.

A manual event is generally used to notify another thread or a thread to notify multiple threads to perform a certain operation. Automatic events are used to protect resources that can only be accessed by one thread at a time. They ensure that only one thread is activated.

Event objects are divided into named event objects and unnamed objects (named and unnamed event objects ).

Functions related to event objects include createevent/openevent, resetevent, setevent, and pulseevent.

Createevent creates or opens an event object. The four parameters are security attributes (kernel objects have security attributes), whether the event is manually reset, and the initial state (true indicates signaled, false indicates nonsignled), the name of the event object. If it is null, create an unnamed event object. If the event with the specified name already exists, the access permission of event_all_access is obtained. The first parameter is valid, and the last two parameters are ignored. Openevent is used to open an existing event (so you can use createevent ). Resetevent/setevent respectively set the event status to nonsignaled and signaled. Pulseevent is untrusted according to msdn, so it is not recommended (equivalent to reset and then set, but not reliable ).

In short, for events, the common methods are createevent, resetevent, and setevent. Then, waitforsingleobject is used to wait for the event (to be singled ).

For the above data competition example, the event solution is as follows:

# Include "stdafx. H "# include <stdio. h> # include <windows. h> # include <process. h> long G = 0; handle g_event; // event handle # define thread_count10 // Number of threads # define access_times100000 // The number of times shared variables are accessed and increased, increase the possibility of data competition void _ cdecl threadproc (void * para) {printf ("sub thread started \ n"); For (INT I = 0; I <access_times; I ++) {waitforsingleobject (g_event, infinite); // wait for event resetevent (g_event); // reset the event so that other threads can continue to wait (equivalent to obtaining the lock) G = G + 1; se Tevent (g_event); // sets the event so that other threads can obtain the event (equivalent to releasing the lock)} printf ("sub thread finished \ n"); _ endthread (); // can be omitted, which is called by implicit .} Int main (INT argc, char * argv []) {g_event = createevent (null, false, false, null); // create the event Kernel Object setevent (g_event ); handle hthread [thread_count]; for (INT I = 0; I <thread_count; I ++) hthread [I] = (handle) _ beginthread (threadproc, 0, null ); for (INT I = 0; I <thread_count; I ++) waitforsingleobject (hthread [I], infinite); // check the result if (G = access_times * thread_count) printf ("correct result! \ N "); else printf (" error result! \ N ");}

Note: during actual operation, we will find that the kernel state event object synchronization method is much less efficient than the user State method. If the access_times here is too large, the running time is much longer than the user mode. Of course, I would like to emphasize that this example is used here to analyze various methods to achieve synchronization. The actual application is obviously a trade-off, different Synchronization Methods have different scenarios.

(3) semaphore

Http://msdn.microsoft.com/zh-cn/site/ms685129 (semaphore objects)

Semaphores are used to count resources. It contains two 32-bit values, one indicating the maximum number of available resources and the other indicating the number of currently available resources.
The semaphore usage rule is as follows: if the current resource quantity is greater than 0, the semaphore signal is sent; if the current resource quantity is 0, the semaphore signal is not sent; the current resource quantity is not allowed to be negative
The current resource quantity cannot exceed the maximum signal quantity
.

When the wait function is called, it checks the current number of resources of the semaphore. If the value is greater than 0, the counter minus 1, and the call thread is in the schedulable state. If the current resource is 0, the thread that calls the function enters the waiting state. When another thread increments the current resource of the semaphore through releasesemaphore, the system will remember the waiting thread and change it to a schedulable state.

For semaphores, functions are createsemaphore, opensemaphore, and releasesemaphore. Waitforsingleobject for semaphores, the side effect of waiting for success is to reduce the semaphores by 1. From a certain point of view, an event is equivalent to a semaphore with a maximum count of 1.

The following example uses a semaphore with the maximum count of 1 to solve the above data competition problem:

# Include "stdafx. H "# include <stdio. h> # include <windows. h> # include <process. h> long G = 0; handle g_semaphore; // semaphore object handle # define thread_count10 // Number of threads # define access_times100000 // The number of times shared variables are accessed and increased, increase the possibility of data competition void _ cdecl threadproc (void * para) {printf ("sub thread started \ n"); For (INT I = 0; I <access_times; I ++) {waitforsingleobject (g_semaphore, infinite); // wait for the semaphore // after obtaining the semaphore, the count will be reduced by 1G = G + 1; releasesemaphore (g_se Maphore, 1, null); // semaphore plus 1} printf ("sub thread finished \ n"); _ endthread (); // can be omitted, which is called by implicit .} Int main (INT argc, char * argv []) {g_semaphore = createsemaphore (null, 0, 1, null); // create a semaphore with an initial count of 0, the maximum count is 1 releasesemaphore (g_semaphore, 1, null); // set the count to 1 handle hthread [thread_count]; for (INT I = 0; I <thread_count; I ++) hthread [I] = (handle) _ beginthread (threadproc, 0, null); For (INT I = 0; I <thread_count; I ++) waitforsingleobject (hthread [I], infinite); // check the result if (G = access_times * thread_count) printf ("correct Result! \ N "); else printf (" error result! \ N ");}

(4) mutex (mutex object, mutex)

Msdn: http://msdn.microsoft.com/zh-cn/site/ms684266 (mutex objects)

The mutex ensures that the thread has mutex access to a single resource.A mutex is similar to a key code area (critical section), but it is a kernel object.. Unlike other kernel objects, the mutex has aThread ownership. It belongs to a thread if it is waiting for success.

Because it is similar to the critical section and the read/write lock, the use is also very similar. Mute-related functions include createmutex, openmutex, and releasemutex. Obviously, create is creation, open is to open an existing name mutex object, and releasemutex is to release the mutex object. Similar to functions in the critical section, waitforsingleobject is used to wait for mutex, which is similar to operations in the critical section.

The Code is as follows:

# Include "stdafx. H "# include <stdio. h> # include <windows. h> # include <process. h> long G = 0; handle g_mutex; // mutex object handle # define thread_count10 // Number of threads # define access_times100000 // The number of times shared variables are accessed and increased, increase the possibility of data competition void _ cdecl threadproc (void * para) {printf ("sub thread started \ n"); For (INT I = 0; I <access_times; I ++) {waitforsingleobject (g_mutex, infinite); // wait for the mutex object G = G + 1; releasemutex (g_mutex); // release the mutex object} printf ("sub Thread finished \ n "); _ endthread (); // can be omitted, which is implicitly called .} Int main (INT argc, char * argv []) {g_mutex = createmutex (null, false, null); // create a mutex kernel object handle hthread [thread_count]; for (INT I = 0; I <thread_count; I ++) hthread [I] = (handle) _ beginthread (threadproc, 0, null); For (INT I = 0; I <thread_count; I ++) waitforsingleobject (hthread [I], infinite); // check the result if (G = access_times * thread_count) printf ("correct result! \ N "); else printf (" error result! \ N ");}

Summary: This section describes how to use synchronization objects and basic functions of user and kernel states. Here, we only want to demonstrate how to use and understand the concept. For actual application, we need to select an appropriate method based on the actual case for synchronization.

Related Articles:

Http://archive.cnblogs.com/a/2223856/

Http://www.cnblogs.com/TravelingLight/archive/2011/10/07/2200912.html

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.