Python mutex lock (Lock): to solve the multi-thread-safety issues

The advantages of multithreading is that concurrency, which can run multiple tasks simultaneously. But when threads need to share data, it may not be synchronized produce "false cases" due to the data, which is a certain randomness by the thread scheduling system caused.

The role of mutex is to solve data-sync issues. About mutex, there is a classic "bank money" problem. Bank money basic process can be divided into the following steps:

  1. Enter the user account password, the system determines the user's account, password match.
  2. User input withdrawal amount.
  3. The system determines whether the account balance is greater than the withdrawal amount.
  4. If the balance is greater than the amount of the withdrawal, the withdrawal successful; if the balance is less than the amount of the withdrawal, the withdrawal fails.


At first glance, this is indeed the withdrawal process in daily life, this process without any problems. But once this process will be placed under multi-threaded scenarios, there is a problem that may arise. Note that it is possible to say here, is not certain. Perhaps you are running a million times without a problem, but the problem did not occur does not mean there is no problem!

Prepared in accordance with the above procedure withdrawal program and use two threads were simulated using two people to get money to do concurrent operation of the same account. Operation here from checking accounts and passwords, only three steps behind the simulation. The following account to define a class, the class encapsulates account balances and account number two member variables.

class the Account:
     # define constructor 
    DEF  the __init__ (Self, account_no, Balance):
         # package account numbers, account balances two member variables 
        self.account_no = account_no 
        self.balance = Balance

Next, a simulation program defined functions withdrawals, according to the number of function operation performed to withdraw money account, money, withdraw money when the logic is not cash account balance is insufficient, when the banknote ejection system balance sufficiently reduce balance .

The main program is very simple, just create an account and start two threads to withdraw money from the account. Procedures are as follows:

Import Threading
 Import Time
 Import the Account
 # define a function to simulate the operation to withdraw money 
DEF Draw (Account, draw_amount):
     # account balance is greater than the number of withdrawals 
    IF account.balance> = draw_amount:
         # ejection banknote 
        Print (threading.current_thread () name. \
             + " withdrawals success spit bill:! " + str (draw_amount))
 #         the time.sleep (0.001) 
        # modify the balance 
        account.balance - = draw_amount
         Print ( " \ t balance is: " + str (account.balance))
    the else :
         Print (. threading.current_thread () name \
             + " to collect the money failed insufficient funds!! " )
 # Create an Account 
acct = Account.Account ( " 1234567 " , 1000 )
 # simulate two threads on the same account to withdraw money 
of the threading.Thread (name = ' A ' , Draw = target, args = (acct, 800 )). Start () 
of the threading.Thread (name = ' b ' , = Draw target, args = (acct, 800)). Start ()

Do the first pipe line 12 in the program code is commented, the above procedure is a very simple logic withdrawals, the logic and withdraw money withdrawals actual operation is also very similar.

Repeatedly run the above program, you will likely see the error results shown in Figure 1.

A successful take money! Spit out the bill: 800
B withdrawals success! Spit out the bill: 800
balance is: 200
balance is: -600

The results shown in the bank is not running the desired results (though it is possible to see the correct operating results), which is multi-threaded programming sudden appearance of "accidental" errors because of the uncertainty thread scheduling.

Assume that the system thread scheduler in the comments in the code at the pause, so that another thread execution (to force pause, as long as the cancellation of the program code comments to comment). After uncommented, run the program again, the results will always see the error shown in Figure 1.

The question arises, account balances out 1,600 yuan when only 1,000 yuan, but there has been a negative account balance, the bank is far from the expected results. While the above procedure is to use artificially  time.sleep(0.001) to force the thread scheduling switch, but the switch is entirely likely to occur (100000 operation as long as there once an error has occurred, and that is caused by programming errors).

Python mutex synchronize threads

The results shown in Figure 1 the error has arisen, because the method body run () method does not have the security thread, the program has two threads concurrently modify Account object, and the system is just switched at the code execution thread comments, switch to another thread to modify the Account object, so there is a problem.

To solve this problem, Python's threading module introduces the mutex lock (Lock). Lock and threading module provides two classes RLock, which provides the following two methods for adding and releasing the mutex mutex:

  1. acquire (blocking = True, timeout = -1): or a request for Lock RLock lock, wherein the lock timeout parameter specifies the number of seconds.
  2. release (): release the lock.


Lock and RLock difference is as follows:

  • threading.Lock: It is a basic lock object can only be locked each time, the rest of the lock requests to wait to obtain the lock is released.
  • threading.RLock: It represents reentrant lock (Reentrant Lock). For reentrant lock, a thread may be the same multiple locking it, may be released several times. If you use RLock, then acquire () and release () method must be paired. If you call n times acquire () lock, you must call the n-th release () to release the lock.


Thus, RLock lock having reentrancy. In other words, the same thread can be locked again has been locked RLock lock, RLock objects maintain a counter to keep track of nested calls acquire () method, the thread after each call to acquire () lock, both you must explicitly call the release () method to release the lock. Therefore, the lock protection section of the same lock method may be further protected method call.

Lock is a tool for controlling multiple threads to access shared resources. Typically, the lock provides exclusive access to shared resources, you can only have one thread lock to Lock objects, the request should be preceded by a thread at the beginning to get access to shared resources of the Lock object. When complete access to shared resources, program releases the lock on the Lock object.

In the thread-safe control is realized, the more commonly used is RLock. RLock commonly used code format is as follows:

class X-:
     # definition requires thread-safe method 
    DEF m ():
         # lock 
        self.lock.acquire ()
         the try :
             # required thread-safe code 
            # ... method body
         # using the finally block to ensure release lock 
        finally :
             # modifications are complete, release the lock 
            self.lock.release ()

Use RLock object to control the security thread, and the lock is released when the lock appears in the role of different ranges generally recommended to use a finally block to ensure that the lock is released when necessary.

Lock the object by using a very easy to achieve thread-safe type, thread-safe type having the following characteristics:

  • Objects of that class can be safely accessed by multiple threads.
  • Each thread after calling any method of the object, you will get the right result.
  • Each thread after calling any method of the object, the object remains reasonable state.


In general, immutable class always thread-safe, because it's the state of the object can not be changed; but mutable objects require additional methods to ensure its thread-safe. For example, the above Account is a variable class, and its self.account_no self._balance (for better packaging, the balance renamed _balance) two member variables can be changed, when two money is altered simultaneously when the value of the member variable self._balance Account object, the program appeared abnormal. Below the Account class access to self.balance is set to be thread-safe, simply to increase control method can modify self.balance thread-safe.

The Account class to read as follows form, it is thread-safe:

Import Threading
 Import Time
 class the Account:
     # define constructor 
    DEF  the __init__ (Self, account_no, Balance):
         # package account numbers, account balances two member variables 
        self.account_no = account_no 
        self._balance = Balance 
        self.lock = threading.RLock ()
     # because the account balance does not allow easily modified, so only getter methods to self._balance 
    DEF getBalance (Self):
         return self._balance
     # provides a thread-safe draw () method to complete the operation to withdraw money 
    DEF Draw (Self, draw_amount):
         # lock
        self.lock.acquire ()
         the try :
             # account balance is greater than the number of withdrawals 
            IF self._balance> = draw_amount:
                 # spit bill 
                Print (threading.current_thread () name \.
                     + " withdrawals success spit bill:! " + str ( draw_amount)) 
                the time.sleep ( from 0.001 )
                 # modify balance 
                self._balance - = draw_amount
                 Print ( " \ T balance is: " + STR (self._balance))
             the else :
                Print (. threading.current_thread () name \
                     + " to collect the money failed insufficient funds!! " )
         a finally :
             # modifications are complete, release the lock 
            self.lock.release ()

The above procedure defines a RLock object. Implemented in the program draw () method when, after entering the process executed immediately request for object locking RLock, when executing the logic withdrawals draw () method, a program finally block used to secure the release of the lock.

Program RLock object as synchronization lock, each thread begins execution draw () method when modifying self.balance, must first lock on RLock object. When the thread completes modifications to self._balance you will want to exit draw () method, then release the lock on the RLock object. This approach is fully in line with "lock → Change → release the lock" security access logic.

When a thread RLock object locking in a draw () method, because the other threads can not get a lock on RLock object, so they performed simultaneously draw () method of self._balance be modified. This means that concurrent threads at any given time only one thread can access shared resources modify the code area (also known as a critical section), so at most only one thread in a critical region at the same time, thus ensuring the security thread.

In order to ensure Lock objects can really "lock" it manages Account object, the program will be written into each object has Lock Account (like a room has a lock as) a corresponding.

The above Account on behalf of a class adds to withdraw money draw () method, and use the Lock object to ensure that the draw () method thread-safe, but canceled setBalance () method (avoid direct modification program self._balance member variable), so thread of execution simply call the Account object's draw () method to perform the operation to withdraw money.

The following program creates two withdrawals and started threads:

Import Threading
 Import the Account
 # define a function to simulate the operation to withdraw money 
DEF Draw (the Account, draw_amount):
     # direct call to account draw objects () method to perform the operation to withdraw money 
    account.draw (draw_amount)
 # Create an Account 
acct = Account. the account ( " 1234567 " , 1000 )
 # simulate two threads on the same account to withdraw money 
of the threading.Thread (name = ' a ' , Draw = target, args = (acct, 800 )). Start () 
of the threading.Thread (name = ' b ' , = Draw target, args = (acct, 800)). Start ()

The above program on behalf of draw thread of execution of () function to get money without having to achieve their own operations, but directly calling the draw account () method to perform the operation to withdraw money. Since the draw () method already use RLock object implements thread-safe, so the above program will not lead to thread-safety issues.

Run the above procedure was repeated several times, always see the results shown in FIG.

A successful take money! Spit out the bill: 800 
    balance is: 200 
B withdraw money failed! Insufficient balance! 

Process finished with exit code 0

 

 

Guess you like

Origin www.cnblogs.com/yangzhen-ahujhc/p/12319306.html