Python-32-Multithread 2

Posted by voitek on Sat, 10 Aug 2019 08:50:06 +0200

  1. Synchronization, Asynchronization, Multithreading, Single Thread, Concurrent, Parallel

    Multi-threading (asynchronism) needs to solve synchronization problem, and single-threading (synchronization) needs to solve asynchronism problem.

    Parallel (without common resources, i.e. multiple CPUs, one CPU per process)

    Concurrent (with common resources, i.e. a CPU, doing multiple things at the same time)

    Parallelism does not need to be solved, concurrency needs to be solved. Multithreading is the solution to concurrency.

     Resolve concurrency:
     
             [The dining hall model, at 12 noon, everyone rushed to the dining hall, which is concurrent. Many people are concurrent.
     
             1. Queue, buffer:
         
                Queue: Queue.
         
                Buffer: A queue formed.
         
                Priority queue: If there is a team of boys and girls, the team of girls is the priority queue.
     
             2. Competition:
         
                 Lock mechanism: scramble for food, someone grabs it, the window can only serve this person at a certain moment, lock the window, that is, lock mechanism.
         
                 Competition is also a high concurrency solution, but some people may not be able to grab it for a long time, so it is not recommended.
     
             3. Pretreatment:
     
                 Statistics show that 80% of the favorite dishes are ready in advance and 20% of the cold dishes are ready now, so that even if someone locks the window, they can be released quickly.
     
                 This is a way of loading data needed by users in advance, preprocessing ideas, caching commonly used.
             
             4. Parallel:
     
                 Open multiple cooking windows and provide services at the same time.
     
                 IT can solve the concurrency problem by purchasing more servers or opening more threads and realizing parallel processing of processes.
     
                 This is a way of thinking of horizontal expansion.
     
                 Note: If threads are processed on a single CPU, they are not parallel.
     
             5. Speed up:
     
                 It is also a way to solve the problem of concurrency by improving the speed of cooking in a single window.
     
                 IT improves the performance of a single CPU, or a single server installs more CPUs.   
     
                 This is an idea of vertical expansion.
     
             6. Message middleware:
     
                 For example, the corridor of Shangdi subway station's nine-winding ileum cushions the flow of people.
     
                 Common message middleware: RabbitMQ, ActiveMQ (Apache), RocketMQ (Ali Apache), kafka(Apache), etc.
    
  2. Background thread and foreground thread

    is/setDaemon(bool): Get/set is a background thread (default foreground thread is False)

    1. If it is a background thread (when we use the setDaemon(True) method and set the sub-thread as a daemon thread), the background thread is also in progress during the execution of the main thread. After the main thread is executed, the background thread stops whether it succeeds or not.

    2. If it is a foreground thread (in fact, setDaemon (False), the main thread execution process, the foreground thread is also in progress, after the main thread execution, waiting for the foreground thread to complete the execution, the program stops.

       if __name__ == '__main__':
           for i in range(4):
               t = MyThread(i)
               t.setDaemon(True)
               t.start()
       
           print('main thread end!')
      
  3. join(): Blocks the current context until the thread calling this method terminates or reaches the specified timeout, even if the setDeamon (True) main thread is set, it still waits for the sub-thread to finish.

    The principle of join is to check whether the thread in the thread pool ends in turn, block until the end of the thread, and jump to execute the join function of the next thread if it ends.

     import threading
     from time import sleep
     lock = threading.Lock()
     def music(i):
         print('i am at music = ',i)
         sleep(1)
         lock.acquire()
         print('Current thread name:{0}'.format(threading.current_thread()))
         lock.release()
         lock.acquire()
         print(threading.current_thread(),'->>>',threading.active_count())
         lock.release()
     
     
     thread_list=[]
     for i in range(5):
         th = threading.Thread(target=music, args=(i,))
         thread_list.append(th)
         th.start()
     for t in thread_list:
         t.join()  # To block the main thread
     
     print('===========This is the main thread=========')
    
  4. Thread Safety

     import threading
     import time
     zero = 0
     lock = threading.Lock()
     def change_zero():
         global zero
         for i in range(5000000):
             lock.acquire()
             zero = zero + 1  # Not atomic operation
             lock.release()
             lock.acquire()
             zero = zero - 1  # Not atomic operation
             lock.release()
     th1 = threading.Thread(target= change_zero())
     th2 = threading.Thread(target= change_zero())
     #  th1 and th2 share change_zero with only one lock
     #  The context of th1 and th2 remembers the state of executing change_zero
     th1.start()
     th2.start()
     th1.join()
     th2.join()
     print(zero)
    

    Maybe when one thread executes, two steps only execute one step x = zero + 1 (i.e., it's not yet time to modify zero), and the GIL lock gives another thread (not familiar with the concept of lock and GIL can read this article first). When the GIL lock returns to the first thread, zero has changed, and then the next thread is executed. zero = x, the result is not zero plus 1.

  5. i add to 500,000 to reduce time

     import threading
     import time
     i = 0
     lock = threading.Lock()
     def add_i():
         global i
         while i < 500000:
             # Stop here and there will be more, that is 500004.
             lock.acquire()
             i += 1
             lock.release()
     if __name__ == '__main__':
         thread_list = []
         for j in range(5):
             th = threading.Thread(target=add_i)
             thread_list.append(th)
             th.start()
     
         for t in thread_list:
             t.join()
     
         print(i)  # 500004
     # [Solving the above problems]
     import threading
     import time
     i = 0
     lock = threading.Lock()
     def add_i():
         global i
         while True:
             lock.acquire()
             if i < 500000:
                 i += 1
                 lock.release()
             else:
                 lock.release()
                 break
     
     if __name__ == '__main__':
         # print(time.perf_counter())
         thread_list = []
         for j in range(50):
             th = threading.Thread(target=add_i)
             thread_list.append(th)
             th.start()
     
         for t in thread_list:
             t.join()
     
         print(i)  # 500000
         # print(time.perf_counter())
    
  6. Lock and RLock

    1. Lock (instruction lock) is the lowest level of synchronization instruction available. Lock is not owned by a specific thread when it is locked. Lock contains two states -- lock and unlock, and two basic methods.

    You can think of Lock as having a lock pool. When a thread requests a lock, it puts the thread into the pool until it gets the lock and exits the pool. Threads in the pool are in a synchronous blocking state in the state diagram.

    1. RLock is a synchronous instruction that can be requested multiple times by the same thread. RLock uses the concepts of "owned threads" and "recursive hierarchies", and when it is locked, RLock is owned by a thread. Threads with RLock can call acquire() again, and release() needs to be called the same number of times when the lock is released.

    It can be considered that RLock contains a lock pool and a counter with an initial value of 0. Each time acquire()/release() is successfully called, the counter will + 1/-1, and the lock will be unlocked at 0.

     # Lock versus Rlock
     import threading
     lock = threading.Lock() #Lock object
     lock.acquire()
     lock.acquire()  #A deadlock was created.
     lock.release()
     lock.release()
     print lock.acquire()
      
      
     import threading
     rLock = threading.RLock()  #RLock object
     rLock.acquire()
     rLock.acquire() #In the same thread, the program will not block.
     rLock.release()
     rLock.release()
    
  7. Condition class

    Condition (condition variable) is usually associated with a lock. When you need to share a lock among multiple Contidion s, you can pass a Lock/RLock instance to the constructor, otherwise it will generate a RLock instance itself.

    It can be assumed that besides the lock pool with Lock, Condition also contains a waiting pool in which threads are waiting to be blocked until another thread calls notify()/notifyAll() notification; upon notification, threads enter the lock pool to wait for locking.

    Construction method:
    Condition([lock/rlock])

    Example method:

    • acquire([timeout])/release(): Calls the corresponding method of the associated lock.

    • wait([timeout]): Calling this method will cause the thread to enter the waiting pool of Condition to wait for notification and release the lock. The thread must be locked before using, otherwise an exception will be thrown.

    • notify(): Calling this method picks a thread from the waiting pool and notifies it that the thread receiving the notification will automatically call acquire() to try to get the lock (into the lock pool); the other threads are still in the waiting pool. Calling this method does not release the lock. The thread must be locked before using, otherwise an exception will be thrown.

    • notifyAll(): Calling this method notifies all threads in the waiting pool that will enter the lock pool to try to get locked. Calling this method does not release the lock. The thread must be locked before using, otherwise an exception will be thrown.

        # encoding: UTF-8
        import threading
        import time
        
        # commodity
        product = None
        # Conditional variable
        con = threading.Condition()
        
        
        # Producer approach
        def produce():
            global product
        
            if con.acquire():
                while True:
                    if product is None:
                        print ('produce...')
                        product = 'anything'
        
                        # Notify consumers that the goods have been produced
                        con.notify()
        
                    # Waiting for Notification
                    # con.wait()
                    time.sleep(2)
        
        
        # Consumer approach
        def consume():
            global product
        
            if con.acquire():
                while True:
                    if product is not None:
                        print('consume...')
                        product = None
        
                        # Notify the producer that the goods are gone
                        con.notify()
        
                    # Waiting for Notification
                    con.wait()
                    time.sleep(2)
        
        
        t1 = threading.Thread(target=produce)
        t2 = threading.Thread(target=consume)
        t2.start()
        t1.start()
      

      Producer-consumer model

        import threading
        import time
        
        condition = threading.Condition()
        products = 0
        
        class Producer(threading.Thread):
            def run(self):
                global products
                while True:
                    if condition.acquire():
                        if products < 10:
                            products += 1
                            print("Producer(%s):deliver one, now products:%s" %(self.name, products))
                            condition.notify()#Do not release the lock, so you need the following sentence
                            condition.release()
                        else:
                            print("Producer(%s):already 10, stop deliver, now products:%s" %(self.name, products))
                            condition.wait()#Automatic release locking
                        time.sleep(2)
        
        class Consumer(threading.Thread):
            def run(self):
                global products
                while True:
                    if condition.acquire():
                        if products > 1:
                            products -= 1
                            print("Consumer(%s):consume one, now products:%s" %(self.name, products))
                            condition.notify()
                            condition.release()
                        else:
                            print("Consumer(%s):only 1, stop consume, products:%s" %(self.name, products))
                            condition.wait()
                        time.sleep(2)
        
        if __name__ == "__main__":
            for p in range(0, 2):
                p = Producer()
                p.start()
        
            for c in range(0, 3):
                c = Consumer()
                c.start()
      

      condition.notifyAll()

        import threading
      
        alist = None
        condition = threading.Condition()
        
        
        def doSet():
            if condition.acquire():
                while alist is None:
                    condition.wait()
                for i in range(len(alist))[::-1]:
                    alist[i] = 1
                    print(alist[i])
                condition.notify()
                condition.release()
        
        
        def doPrint():
            if condition.acquire():
                while alist is None:
                    condition.wait()
                for i in alist:
                    print(i)
        
                print()
                condition.notify()
                condition.release()
        
        
        def doCreate():
            global alist
            if condition.acquire():
                if alist is None:
                    alist = [0 for i in range(10)]
                    condition.notifyAll()
                condition.release()
        
        
        tset = threading.Thread(target=doSet, name='tset')
        tprint = threading.Thread(target=doPrint, name='tprint')
        tcreate = threading.Thread(target=doCreate, name='tcreate')
        tset.start()
        tprint.start()
        tcreate.start()
      

Topics: Apache Windows RabbitMQ kafka