Note: Double detection lock is not reliable

zhaozj2021-02-16  56

I have recently seen some articles on the model implementation, I found that when I realize the Singleton mode, whether it is C , or Java, or Object Pascal, there is no exception to mention "Double Detection Lock", "Name" is not affecting access speed Solved concurrent conflicts, in fact, this method is not reliable.

The principle of "dual detection lock" is roughly, when trying to obtain resources, does not lock operation; if resource allocation is found, enter the mutually exclusive area, then judge once, if it is still not initialized, then initialization operation. Since the modification operation is completed in the mutual exclusion, there will be no two threads (or other operating units) simultaneously modify, avoiding the modified concurrent conflict. The pseudo code is described as follows:

Var _INSTANCE: Instance = nil function get_instance if _instance = nil dam; if _instance = nil dam

The bold character part is a mutually exclusive area, and only the same thread can enter at the same time. The problem is that although there is only one thread to mutually exclusive zone at the same time, it does not guarantee that other threads do not allow other threads to enter non-mutually exclusive. In the mutual exclusive area, if the assignment of _instance is not an atomic operation, it will no longer be reliable in non-mutex.

Why do you say this way? Suppose we work in an ideal concurrent environment, the compiler will not optimize the _instance, nor will it disrupt the code order, and there is no synchronization problem of the cache of the memory, ... Now we simulate two threads. Operation:

Thread a enters GET_INSTANCE

Thread a Remove the value of _instance

Thread a Judgment _instance = nil, result is TRUE

Thread a LOCK;

Thread a Remove the value of _instance

Thread a Judgment _instance = nil, result is TRUE

Thread a call CREATE_INSTANCE, success

Thread a Save the result to _instance, first step

Thread A hangs

Thread B enters GET_INSTANCE

Thread B removes the value of _instance

Thread B judge _instance = nil, result is false (it is very likely, because _instance has changed)

Thread B returns _instance

Thread B uses the return value

Thread B ...

Thread B hangs

Thread a awakening

Thread a Save the results to _Instance, the second step

Thread a unlocked

Thread a ...

Now look at it, what is the result of thread b call GET_INSTANCE? Don't ask me, I really don't know. The key is that the thread A modifies the initial interruption, and there is no to prevent thread B from accessing _instance, resulting in illegal states that _instance in a period of time. If the assignment of _instance is an atomic operation, there is no problem, but many reasons can make it not an atomic operation.

So how do you solve this problem? The above has been mentioned, resulting in two conditions in this question: First, the assignment of _instance is not an atomic operation; the other is that other threads can enter non-interracial when a thread enters the mutex. The solution is naturally any one of these two conditions.

Make the value of _instance becomes a very good choice, but because the type of _instance cannot be arranged casually, it may not be able to implement (but as if JVM guarantees access to 32-bit data is atomic operation, I don't know this guarantee How big is the strength), but we can change a thinking, just not to judge the value of _instance, change to judge a flag that can be assigned with an atomic operation (assuming can use Boolean type): var

_INSTANCE: Instance = NIL

_INITED: BOOLEAN = FALSE

Function Get_instance

IF not _InInited Then

LOCK;

IF not _InInited Then

_INSTANCE = CREATE_INSTANCE

Call atomset (_Ind, true)

END IF

UNLOCK;

END IF

Result = _INSTANCE;

END FUNCTION

Since the assignment of the _Inited flag is atomic operation, it will not be interrupted, so when it is judged in front of the front, _INITED is always valid.

If you can't find this type, there is also a workaround:

VAR

_INSTANCE: Instance = NIL

_INITED: INTEGER = 0

Function Get_instance

if _Inited = 0 Then // If it is not 0, it is interrupted when it is assigned to it, and the value in _Instance is also complete and effective.

LOCK;

IF _INSTANCE = NIL THEN / / Due to the mutual exclusive area, there will be no other thread hang in the mutex, this judgment is reliable.

_INSTANCE = CREATE_INSTANCE

_INITED = 1

END IF

UNLOCK;

END IF

Result = _INSTANCE;

END FUNCTION

In this way, if a thread is interrupted by the action of _instance assignment, _INITED = 0 judgment is true, the current thread will call the Lock to block; if a thread is interrupted by _Inited assignment operation, then then _ It is already a legitimate valid value in Instance, and there will be no problem no matter whether it _Inited = 0 is established.

Talking about the method of assigning a atomic operation, and another method is of course to make other threads cannot access the _instance when a thread handles the mutex. At the same time, we must allow all threads to access _instance at the same time without threads, if you have some experience in concurrency control, you can think that - completely correct, using multi_read_exclusive_write's lock mechanism. This will ensure that there is no other thread to read _instance when a thread modifies _instance. But when you implement it, you should also be kill, you can't:

VAR

_INSTANCE: Instance = NIL

Function Get_instance

Begin_read

IF not _InInited Then

Begin_write

IF not _InInited Then

_INSTANCE = CREATE_INSTANCE

Call atomset (_Ind, true)

END IF

END_WRITE

END IF

Result = _INSTANCE;

END_READ

End function If this is implemented, it is possible to perform begin_read, then thread a execution begin_write, blocked-wait thread B execute end_read; thread B will also perform begin_write, also blocked - Wait a thread A to execute End_Write ......

The correct implementation is this:

VAR

_INSTANCE: Instance = NIL

Function Get_instance

Begin_read

IF not _InInited Then

END_READ

Begin_write

IF not _InInited Then

_INSTANCE = CREATE_INSTANCE

Call atomset (_Ind, true)

END IF

END_WRITE

Begin_read

END IF

Result = _INSTANCE;

END_READ

END FUNCTION

转载请注明原文地址:https://www.9cbs.com/read-26257.html

New Post(0)