Secure Thread Synchronization

Source: Internet
Author: User
So far, thread synchronization is most commonly used to ensure that multiple threads are mutually exclusive to shared resources. For synchronizing multiple threads in a single process for mutex access, the critical_section structure of Win32 API and its related functions provide the fastest and most efficient way, Microsoft. net Framework does not provide the critical_section structure, but provides a similar mechanism-this mechanism uses the system. threading. monitor class and syncblock are implemented.
In this column, I will explain how. NET Framework supports the general purpose of thread synchronization. In addition, I would like to explain the motivations for syncblock and monitor being designed as they are and how they work. Finally, I want to explain why the design is poor and how to use the mechanism in a correct and secure way.

Brilliant idea

. NET Framework adopts the OOP structure. This means that the developer constructs an object and calls a member of the type to manipulate it. However, sometimes these objects are manipulated by multiple threads. Therefore, to ensure that the object state is not damaged, thread synchronization is required. When designing the. NET Framework, Microsoft designers decided to create a new mechanism for developers to easily synchronize an object.
The basic idea is that every object on the stack is associated with a data structure that can be used for thread synchronization (very similar to Win32 critical_section ). Then, FCL (framework class library, framework class library) provides some methods -- when you pass an object reference, this data structure is used to synchronize threads.
If this design is applied to an unmanaged C ++ class under Win32, the class looks like this:

. A critical_section for every object
Class sometype {
PRIVATE:
// Associate a private critical_section field with each object
Critical_section m_csobject;

Public:
Sometype (){
// Initialize the critical_section field in the constructor.
Initializecriticalsection (& m_csobject );
}

~ Sometype (){
// Delete the critical_section field in the destructor
Deletecriticalsection (& m_csobject );
}

Void somemethod (){
// In this function, we use the critical_section field of the object.
// Synchronize multiple threads to access this object
Entercriticalsection (& m_csobject );
// The thread-safe code can be executed here...
Leavecriticalsection (& m_csobject );
}

Void anothermethod (){
// In this function, we use the critical_section field of the object.
// Synchronize multiple threads to access this object
Entercriticalsection (& m_csobject );
// The thread-safe code can be executed here...
Leavecriticalsection (& m_csobject );
}
};

Essentially,. NET Framework associates a field similar to critical_section for each object and is responsible for initializing and deleting it. The only thing developers need to do is add some code to the method that requires thread synchronization to enter and leave the field.

A wonderful idea

Now, it is obvious that it is a waste to associate a critical_section field with each object on the stack, especially because most objects do not require thread-safe access. Therefore, the. NET Framework Team designed a more efficient way to provide the features described above. The following describes how it works:
When the CLR (Public Language Runtime) is initialized, it allocates a cache zone for the syncblock. A syncblock is a memory block that can be associated with any object as needed. Syncblock has the same fields as Win32 critical_section.
Each object on the stack is associated with two additional fields at creation. The first is methodtablepointer (method table pointer), which contains the address of the method table of this type. Basically, this pointer allows you to obtain the type information of each object on the stack. In fact, when you call the GetType method of system. object, this method determines the object type through the methodtablepointer field of the object. Another extra field, called syncblockindex, contains a 32-bit signed integer, which leads to a syncblock in the syncblock cache.
When an object is constructed, its syncblockindex field is initialized to a negative value, indicating that it does not point to any syncblock at all. Then, when a method is called to synchronize access to the object, CLR searches for an idle syncblock in the syncblock cache and points the syncblockindex field of the object to it. In other words, syncblock is associated with an object only when a field needs to be synchronized. When no thread can access the object synchronously, The syncblockindex field of the object will be reset to a negative value and the corresponding syncblock will be released, so that it can be re-associated to other objects in the future.
Figure 2.. net Thread Synchronization in


Figure 2Is the specific form of this idea. InCLR Data StructuresAs you can see, each type of the system has a corresponding data structure. You can also see a set of syncblock structures. InManaged heapYou can see that three objects (objecta, objectb, and objectc) are created. The methodtablepointer field of each object points to the corresponding type of method table. You can obtain the type of each object through the method table. Therefore, we can easily see that objecta and objectb are sometype instances, while objectc is anothertype instances.
The syncblockindex field of objecta is set to 0, which indicates that syncblock #0 is currently being used by objecta. On the other hand, the syncblockindex of objectb is set to-1, indicating that no syncblock is associated with it. Finally, the syncblockindex of objectc is 2, indicating that it is using syncblock #2. In this example, syncblock #1 is not used, but may be associated with an object in the future.
Therefore, logically, each object on the stack is associated with a syncblock, which can be used for fast mutually exclusive thread synchronization. However, physically, syncblockOnly whenIt is associated with an object only when necessary, and is detached from the object when the object no longer needs it. This means that the memory usage is efficient. In addition, if necessary, the syncblock cache can create more syncblocks, so you don't have to worry that it will be exhausted due to too many synchronized objects at the same time.

Use MonitorTo manipulate syncblock

Now that you have understood syncblock, let's take a look at how to lock an object. To lock or unlock an object, you can use the system. Threading. Monitor class. All methods of this type are static. Call the following method to lock an object:

Public static void enter (Object OBJ );

When you call the enter method, it first checks whether the specified object's syncblockindex is negative. If yes, this method finds an idle syncblock from the syncblock cache and saves its index to the syncblockindex of the object. Once the syncblock is associated with the object, the enter method checks the syncblock of the object to see if another thread currently owns the syncblock. If no process has it, the thread that calls enter becomes the owner of the object. On the other hand, if another thread already owns the syncblock, the thread that currently calls enter will be suspended until that thread releases ownership of the syncblock.
If you want to write more defensive code, you can call one of the following tryenter methods:

Public static Boolean tryenter (Object OBJ );

Public static Boolean tryenter (Object OBJ,
Int millisecondstimeout );

Public static Boolean tryenter (Object OBJ,
Timespan timeout );

The first tryenter simply checks whether the thread that calls it can obtain the ownership of the syncblock of the object. If it succeeds, true is returned. The other two methods allow you to specify a wait time, which indicates how long you allow the thread to wait for ownership. If the ownership cannot be obtained, false is returned for all three methods.
Once ownership is obtained, the code can securely access the object's fields. After the access is completed, the thread should also call exit to release the syncblock:

Public static void exit (Object OBJ );

If the thread does not obtain the ownership of the syncblock of the specified object, calling exit throws a synchronizationlockexception. Also note that the thread can recursively obtain the ownership of a syncblock. Each successful enter must correspond to an exit, in this way, the syncblock will be released (this is the same as the critical_section of Win32-translator ).

Synchronizing the Microsoft Way

Now let's take a lookFigure 3It shows how to use the enter and exit methods of monitor to lock and unlock an object. Note: to implement the lasttransaction attribute (property), you must call enter, exit, and a temporary variable DT. This is very important, so as to avoid returning a corrupted value. This happens if another thread accesses this attribute when calling javasmtransaction.

Use C #LockStatement to simplify the code

Because this "Call enter -- access protected resources -- call exit" mode is so common, C # simply provides special syntax to simplify this code.Figure 4The two C # code snippets of the same function, but the latter is more concise. Using the lock Statement of C #, you can fully simplify the transaction class. Pay special attentionFigure 5The improved lasttransaction attribute shown in, which no longer requires temporary variables.
In addition to simplifying the code, the lock statement ensures that monitor. Exit will be called, so that the syncblock will be released even if an exception occurs in the try block. You should always combine the exception handling and synchronization mechanisms to ensure that the lock is correctly released. However, if you use the lock Statement of C #, the compiler will automatically generate the correct code for you. In addition, Visual Basic. Net has a synclock statement similar to the lock Statement of C #. They do the same thing.

Synchronizing static members the Microsoft Way

The transaction class demonstrates how to synchronously access the instance fields of an object. However, what if your type defines some static fields and the static functions that access these fields? In this case, there is no such instance on the stack, so there is no available syncblock or an object reference passed to the monitor. Enter and monitor. Exit methods.
In fact, a memory block containing a type descriptor is a heap object.Figure 2Does not show this, but the memory block occupied by the sometype type descriptor and anothertype descriptor is actually an object, and there are also methodtablepointer and syncblockindex fields. This means that syncblock can be associated with a type, and the reference of a type object (type object, which refers to an object that describes a type) can be passed to the enter and exit methods of monitor. InFigure 6In the transaction class shown in, all the Members are changed to static, and the performtransaction method and lasttransaction attributes are also changed to show how Microsoft wants developers to synchronize access to static members.
In the performtransaction method and lasttransaction attributes, you will no longer see the this keyword, because it cannot be used in static members. I will pass the reference of the type descriptor object to the lock statement. This reference is obtained through the typeof operator of C #. The typeof operator returns the reference of the object descriptor of the specified object. In Visual Basic. net, the GetType operator has the same function.

Why is it not that brilliant?

As you can see, the idea of logically associating each object on the stack with a data structure for synchronization sounds good. But this is actually a bad idea. Let me explain the reason. Do you still remember the unmanaged C ++ code shown at the beginning of this article? If you write it, will you set the access permission of the critical_section field to public? Of course not. That's ridiculous. Setting this field to public will allow any code in the program to manipulate the critical_section structure, so that malicious code can easily deadlock any thread that uses this type of instance.
Er... guess what happened -- syncblock is just like a public field! Any piece of code can transmit any object reference to the enter and exit methods of monitor at any time. In fact, any type descriptor reference can also be passed to the monitor method.
Figure 7The code shows how bad this situation is. Here, an app object is created in the main method, the object is locked, and a garbage collection occurs at a certain time point (in this code, a garbage collection is forced ), when the Finalize method of the app is called, it tries to lock the object, but the CLR finalize thread cannot lock the object because the main thread of the program has locked the object. This causes the CLR finalize thread to stop-so no other objects can be finalize in the current process (which may contain multiple AppDomains, there are no other finalize objects in the heap memory.
Fortunately, there is a solution, but that means you have to leave Microsoft's design and suggestions behind. Instead, define a private system. the object field acts as a member of your type, constructs it, and passes its reference to the lock Statement of C # Or Visual Basic. net synclock statement.Figure 8Demonstrate how to override the transaction class so that the objects used for synchronization become private members of the class. Similarly,Figure 9Demonstrate how to override a transaction class when all its members are static.
It seems that constructing a system. Object object just for synchronization is weird. I feel that Microsoft's design for the monitor class is inappropriate. You should construct a monitor-type instance for each type you want to synchronize (the original text is type, the suspect is object. In this way, static methods (static methods of the monitor class) will become instance methods without the system. object parameter. This will solve all the problems and fully simplify the developer's programming model.
In addition, if you create complex types with many fields, your methods and attributes may only need to lock a subset of these fields at any time. You can always lock a specified field by referencing it to lock or monitor. Enter. Of course, if the field is private (I always suggest this), I will only consider this. If you want to lock several fields together, you can always pass one of them to lock or enter. Alternatively, you can construct a system. Object object. Its unique intent is to lock a set of fields.LockThe more detailed the segment (critical segment), the better the Code Performance and testability [1].
[1] Note: we should keep the lock segment short. In other words, a lock segment should execute as few code as possible, in this way, the wait time of other threads in the lock (critical) zone can be as short as possible, and the possibility of deadlock is also less.

Unboxed) Value Type instance

Before I end this column, I want to point out a synchronization bug. It took me several hours to track it for the first time. The following code snippet demonstrates this problem:

Class anothertype {

// An unboxed (unboxed) Boolean Value Type instance
Private Boolean flag = false;
Public Boolean flag {
Set {
Monitor. Enter (FLAG); // pack and lock the flagBoxed object
Flag = value; // the actual value is not protected.
Monitor. Exit (FLAG); // pack the flag and try to unlockBoxed object
}
}
}

You may be surprisedNo thread synchronization occurs in this Code.! The reason is: flag is an unboxed Value Type instance, rather than a reference type. The unboxed Value Type instance does not have the two additional fields methodtablepointer and syncblockindex. This means that an unboxed Value Type instance cannot have a syncblock associated with it.
The enter and exit methods of monitor require a reference to the object on the stack. When C #, Visual Basic. net and many other compilers see a piece of code trying to pass unboxed Value Type instances to methods that require object reference, they will automatically generate code to bind the value type to the instance (box ). The boxed instance (on the heap) will have a methodtablepointer and a syncblockindex, which can be used for thread synchronization. However, every time such a function is called, a new packing is performed, that is, a new boxed instance is generated. This instance is different from the previously boxed instance, that is, each lock and unlock operation is a different object.
For example, in the code snippet above, when the Set Method of the Flag attribute is called, it calls the enter method of monitor. Enter requires a reference type, so the flag is boxed and the reference of the boxed object is passed to enter. The syncblock of this object is now owned by the calling thread. If another thread needs to access this attribute now, the flag will be reboxed to generate a new object with its own syncblock. In addition, calling exit also causes a packing operation.
As I said before, it took me several hours to discover the problem. If you want to synchronize access to an unboxed Value Type instance, you must allocate a system. Object object and use it for synchronization.Figure 10The code in is correct.
In addition, if you use the lock Statement of C # To replace monitor. Enter and monitor. Exit, the C # compiler will help you avoid unexpected attempts to lock a value type. When you pass an unboxed Value Type instance to the lock statement, the C # compiler reports an error. For example, if you try to pass a Boolean (bool in C #) to the lock statement, you will see the following error: "error cs0185: 'bool 'is not a reference type as required by the lock statement ". In Visual Basic. net, if you use an unboxed Value Type instance for the synclock statement, the compiler also reports an error: "error bc30582: 'synclock 'operand cannot be of Type 'boolean' because 'boolean' is not a reference type ".


Figure 3. Using enter and exit Methods
Class transaction {

// Private field holding the time
// The last transaction completed MED
Private datetime timeoflasttransaction;

Public void implements mtransaction (){
// Lock this object
Monitor. Enter (this );

// Perform the transaction...

// Record time of the most recent transaction
Timeoflasttransaction = datetime. now;

// Unlock this object
Monitor. Exit (this );
}

// Public read-only property returning
// The Time of the last transaction
Public datetime lasttransaction {
Get {
// Lock this object
Monitor. Enter (this );

// Save the time of the last transaction
// In a Temporary Variable
Datetime dt = timeoflasttransaction;

// Unlock this object
Monitor. Exit (this );

// Return the value in the Temporary Variable
Return (DT );
}
}
}

. Regular and simple lock and unlock
// Regular Function
Public void somemethod (){
// Lock the object
Object otemp = this;
Monitor. Enter (otemp );
Try {
// Access the object
...
// Unlock the object
}
Finally {
Monitor. Exit (otemp );
}
// Return
}

// Simple Function
Public void somemethod (){
// Lock the object
Lock (this ){

// Access the object
...
// Unlock the object
}

// Return
}

. Transaction class
Class transaction {

// Private field holding the time
// The last transaction completed MED
Private datetime timeoflasttransaction;

Public void implements mtransaction (){
Lock (this ){
// Perform the transaction...

// Record time of the most recent transaction
Timeoflasttransaction = datetime. now;
}
}

// Public read-only property returning
// The Time of the last transaction
Public datetime lasttransaction {
Get {
Lock (this ){
// Return the time of the last transaction
Return timeoflasttransaction;
}
}
}
}

. New transaction class
Class transaction {

// Private field holding the time
// The last transaction completed MED
Private Static datetime timeoflasttransaction;

Public static void implements mtransaction (){
Lock (typeof (transaction )){
// Perform the transaction...

// Record time of the most recent transaction
Timeoflasttransaction = datetime. now;
}
}

// Public read-only property returning
// The Time of the last transaction
Public static datetime lasttransaction {
Get {
Lock (typeof (transaction )){
// Return the time of the last transaction
Return timeoflasttransaction;
}
}
}
}

. Threads banging heads
Using system;
Using system. Threading;

Class app {
Static void main (){
// Construct an instance of the app object
APP a = new app ();

// This malicious code enters a lock on
// The object but never exits the lock
Monitor. Enter ();

// For demonstration purposes, let's release
// Root to this object and force a garbage collection
A = NULL;
GC. Collect ();

// For demonstration purposes, wait until all finalize
// Methods have completed their execution-deadlock!
GC. waitforpendingfinalizers ();

// We never get to the line of code below!
Console. writeline ("leaving main ");
}

// This is the App Type's finalize Method
~ APP (){
// For demonstration purposes, have the CLR's
// Finalizer thread attempt to lock the object.
// Note: Since the main thread owns the lock,
// The finalizer thread is deadlocked!
Lock (this ){
// Pretend to do something in here...
}
}
}

. Transaction with private object
Class transaction {

// Private object field used
// Purely for synchronization
Private object objlock = new object ();

// Private field holding the time
// The last transaction completed MED
Private datetime timeoflasttransaction;

Public void implements mtransaction (){
Lock (objlock ){
// Perform the transaction...

// Record time of the most recent transaction
Timeoflasttransaction = datetime. now;
}
}

// Public read-only property returning
// The Time of the last transaction
Public datetime lasttransaction {
Get {
Lock (objlock ){
// Return the time of the last transaction
Return timeoflasttransaction;
}
}
}
}

. Transaction with static members
Class transaction {

// Private, static object field
// Used purely for synchronization
Private Static object objlock = new object ();

// Private field holding the time
// The last transaction completed MED
Private Static datetime timeoflasttransaction;

Public static void implements mtransaction (){
Lock (objlock ){
// Perform the transaction...

// Record time of the most recent transaction
Timeoflasttransaction = datetime. now;
}
}

// Public read-only property returning
// The Time of the last transaction
Public static datetime lasttransaction {
Get {
Lock (objlock ){
// Return the time of the last transaction
Return timeoflasttransaction;
}
}
}
}

. Now there's Synchronization
Class anothertype {

// An unboxed Boolean Value Type
Private Boolean flag = false;

// A private object field used
// Synchronize access To the flag field
Private object flaglock = new object ();

Public Boolean flag {
Set {
Monitor. Enter (flaglock );
Flag = value;
Monitor. Exit (flaglock );
}
}
}

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.