Deadlock Example
Multi-threading often encounter deadlock problems, learning the operating system will talk about the deadlock related things, we use Python visual demonstration.
One reason for a deadlock is a mutex. Assuming a banking system, user A tries to transfer 100 dollars to User B, while User B tries to transfer 200 to user A, which may result in a deadlock.
2 threads wait for each other's locks, occupying resources without releasing each other.
#Coding=utf-8Import TimeImportThreadingclassAccount :def __init__(self, _id, balance, lock): Self.id=_id self.balance=Balance Self.lock=LockdefWithdraw (self, amount): Self.balance-=AmountdefDeposit (Self, amount): Self.balance+=AmountdefTransfer (_from, to, amount):if_from.lock.acquire ():#lock your account._from.withdraw (amount) Time.sleep (1)#make trading time longer, 2 trading threads overlap on time, enough time to create deadlocks Print 'wait for lock ...' ifTo.lock.acquire ():#lock each other's accountto.deposit (amount) To.lock.release () _from.lock.release ()Print 'finish ...'a= Account ('a', 1000, Threading. Lock ()) b= Account ('b', 1000, Threading. Lock ()) threading. Thread (Target= transfer, args = (A, B, 100) . Start () threading. Thread (Target= transfer, args = (b, A, ()). Start ()
Locking mechanism for preventing deadlocks
Problem:
You are writing a multithreaded program in which threads need to acquire multiple locks at once, and how to avoid deadlock problems at this time.
Solution:
In multithreaded programs, a large part of the deadlock problem is caused by threads acquiring multiple locks at the same time. For example, when a thread acquires the first lock and then blocks when it acquires a second lock, the thread can block the execution of other threads, causing the entire program to feign death. One solution to the deadlock problem is to assign a unique ID to each lock in the program, and then allow only the use of multiple locks in ascending rules, which is very easy to implement using the context manager, as shown in the following example:
ImportThreading fromContextlibImportContextManager#thread-local state to stored information on locks already acquired_local =threading.local () @contextmanagerdefAcquire (*locks):#Sort Locks by object identifierLocks = sorted (locks, key=Lambdax:id (x))#Make sure lock order of previously acquired locks are not violatedacquired = GetAttr (_local,'acquired',[]) ifAcquired andMax (ID (lock) forLockinchAcquired) >=ID (locks[0]):RaiseRuntimeError ('Lock Order violation') #acquire all of the locksacquired.extend (locks) _local.acquired=acquiredTry: forLockinchLocks:lock.acquire ()yield finally: #Release Locks in reverse order of acquisition forLockinchReversed (locks): Lock.release ()delAcquired[-len (locks):]
How do I use this context manager? You can create a lock object in a normal way, but the acquire () function is used to request a lock in either a single lock or multiple locks, as shown in the following example:
ImportThreadingx_lock=Threading. Lock () Y_lock=Threading. Lock ()defthread_1 (): whileTrue:with Acquire (X_lock, Y_lock):Print('Thread-1') defthread_2 (): whileTrue:with Acquire (Y_lock, X_lock):Print('Thread-2') T1= Threading. Thread (target=thread_1) T1.daemon=Truet1.start () T2= Threading. Thread (target=thread_2) T2.daemon=Truet2.start ()
If you execute this code, you will find that it does not have a deadlock even if it acquires a lock in a different order in different functions. The key is that in the first piece of code, we sort the locks. By sorting, the locks are retrieved in a fixed order, regardless of the order in which they are requested. If more than one acquire () operation is nested, a potential deadlock problem can be detected through thread local storage (TLS). Let's say your code is written like this:
ImportThreadingx_lock=Threading. Lock () Y_lock=Threading. Lock ()defthread_1 (): whileTrue:with Acquire (X_lock): With Acquire (Y_lock):Print('Thread-1') defthread_2 (): whileTrue:with Acquire (Y_lock): With Acquire (X_lock):Print('Thread-2') T1= Threading. Thread (target=thread_1) T1.daemon=Truet1.start () T2= Threading. Thread (target=thread_2) T2.daemon=Truet2.start ()
If you run this version of the code, there must be a thread crash, and the exception information might look like this:
ExceptioninchThread Thread-1: Traceback (most recent): File"/usr/local/lib/python3.3/threading.py", Line 639,inch_bootstrap_inner self.run () File"/usr/local/lib/python3.3/threading.py", line 596,inchRun Self._target (*self._args, * *Self._kwargs) File"deadlock.py", line 49,inchThread_1 with Acquire (y_lock): File"/usr/local/lib/python3.3/contextlib.py", line 48,inch __enter__ returnNext (Self.gen) File"deadlock.py", line 15,inchAcquireRaiseRuntimeError ("Lock Order violation") Runtimeerror:lock Order violation>>>
The reason for the crash is that each thread records the locks that it has acquired. The Acquire () function examines the list of locks that were previously acquired, because the locks are obtained in ascending order, so the function will assume that the ID of the previously acquired lock must be less than the newly requested lock, and the exception will be triggered.
Discuss
Deadlocks are a problem for every multithreaded operation (as it is a common topic for every operating-system textbook). As a matter of experience, it is possible to ensure that each thread can hold only one lock at a time, so that the program is not plagued by deadlock problems. Once the thread has applied for multiple locks at the same time, everything is unpredictable.
Deadlock detection and recovery is an extended topic with virtually no elegant solutions. A more common deadlock detection and recovery scheme is the introduction of watchdog counters. When the thread is running normally, it resets the counter every once in a while, without a deadlock, everything works. Once a deadlock occurs, the timer expires because the counter cannot be reset, and the program resumes itself to a normal state by rebooting itself.
Avoiding deadlocks is another way to resolve deadlock problems, which are obtained in ascending order of the object ID when the process acquires the lock, and are mathematically proven so that the program does not enter a deadlock state. The proof is left to the reader as an exercise. The main idea of avoiding deadlocks is that simply adding locks in ascending order of object IDs does not produce circular dependencies, and cyclic dependencies are a necessary condition for deadlocks, which prevents the program from entering a deadlock state.
The following is a classic question about thread deadlock: "The question of dining philosophers" as the last example of this section. The question is this: five philosophers sit around a table, with a bowl of rice and a chopstick in front of each person. Here each philosopher can be regarded as a separate thread, and each chopstick can be regarded as a lock. Each philosopher can sit, think, and eat in one of three states. It is important to note that every philosopher eats two chopsticks, so the question comes: if every philosopher picks up his left chopsticks, then all five of them can only sit with one chopstick until they starve to death. At this point they entered the deadlock state. The following is a simple use of deadlock avoidance mechanism to solve the "philosopher dining problem" implementation:
ImportThreading#The philosopher threaddefphilosopher (left, right): whileTrue:with Acquire (left,right):Print(Threading.currentthread (),'Eating') #The chopsticks (represented by locks)Nsticks = 5Chopsticks= [Threading. Lock () forNinchrange (nsticks)]#Create All of the philosophers forNinchRange (nsticks): t= Threading. Thread (target=philosopher, args= (chopsticks[n],chopsticks[(n+1)%Nsticks])) T.start ()
Examples of deadlock formation in Python and the prevention of deadlock conditions