python network-multithreading (22)

Keywords: Python Windows less Programming

1. What is a thread

Threads (English: thread) are the smallest unit that the operating system can schedule operations.It is included in the process and is the actual unit of operation in the process.Threads in the same process will share all the system resources in the process. A process can have many threads, each thread executes different tasks in parallel.

 

2. Differences between threads and processes

1. Examples:

  • A process that can accomplish multiple tasks, such as running multiple QQ s simultaneously on one computer
  • Threads, able to accomplish multiple tasks, such as multiple chat windows in a QQ

2. Different definitions

  • Processes are an independent unit of the system for resource allocation and scheduling. The focus is on resource allocation and scheduling
  • Threads are an entity of a process and the basic unit for CPU scheduling and allocation. Threads are smaller and can run independently than processes.

3. Different functions

  • A program has at least one process and a process has at least one thread.
  • Threads are partitioned at a smaller scale than processes (with fewer resources than processes), which makes multithreaded programs more concurrent.
  • Processes have separate memory units during execution, and multiple threads share memory, which greatly improves program efficiency
  • Threads cannot execute independently and must depend on the process

4. Advantages and disadvantages

  • Threads and processes have their own advantages and disadvantages in use: threads have a small execution overhead, but are detrimental to resource management and protection; processes have the opposite effect.

 

3. threading module

1. Single-threaded execution

#coding=utf-8
import time

def sayHi():
    print("Hello I am Se7eN_HOU")
    time.sleep(1)

if __name__ == "__main__":
    for i in range(5):
        sayHi()

The results are:

2. Multithreaded Execution

#coding=utf-8
import threading
import time

def sayHi():
    print("Hello I am Se7eN_HOU")
    time.sleep(1)

if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target = sayHi)
        t.start()

The results are:

Explain

  1. It is clear that multithreaded concurrent operations are used, which takes much less time
  2. The created thread needs to be started by calling the start() method

3. View the number of threads

#coding=utf-8
import threading
from time import sleep,ctime

def sing():
    for i in range(3):
        print("Singing...%d"%i)
        sleep(1)

def dance():
    for i in range(3):
        print("Dancing...%d"%i)
        sleep(1)

if __name__ == '__main__':
    print("---start---")

    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)

    t1.start()
    t2.start()

    while True:
        length = len(threading.enumerate())
        print("The number of threads currently running is:%d"%length)
        if length<=1:
            break
        sleep(0.5)

The results are:

 

4. Encapsulation of Thread Subclasses

Multitask program development can be accomplished by using threading modules. To make each thread more encapsulated, when using threading modules, a new sub class is often defined, as long as threading.Thread is inherited, then the run method is overridden

#coding=utf-8
import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name Property holds the name of the current thread
            print(msg)

class MyThread2(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name Property holds the name of the current thread
            print(msg)

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()

    t1.start()
    t2.start()

The results are:

Explain

  • python's threading.Thread class has a run method that defines the functional functions of threads and can be overridden in its own thread class.
  • After creating your own instance of a thread, you can start it through the start method of the Thread class, and when the thread gets an opportunity to execute, the run method is called to execute the thread.

2. Execution order of threads

#coding=utf-8
import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name Property holds the name of the current thread
            print(msg)

def test():
    for i in range(1,5):
        t=MyThread()
        t.start()

if __name__ == '__main__':
   test()

The results are:

Explain:

From the code and execution results, we can see that the execution order of multithreaded programs is uncertain.When a sleep statement is executed, the thread is Blocked until it is finished, and the thread enters a ready (Runnable) state, waiting to be dispatched.Thread scheduling will choose a thread to execute.The code above only guarantees that each thread will run the entire run function, but the starting order of the thread and the execution order of each loop in the run function are not determined.

Summary:

  1. Each thread must have a name, and although the name of the thread object is not specified in the above example, python automatically assigns a name to the thread.
  2. The thread completes when its run() method ends.
  3. The thread dispatcher cannot be controlled, but there are other ways to influence how the thread is dispatched.

Several states of threads

 

 

5. Multi-threaded-global variables (shared), local variables (not shared)

1. Sharing global variables

from threading import Thread
import time

g_num = 100

def work1():
    global g_num
    for i in range(3):
        g_num += 1
        print("----in work1, g_num is %d---"%g_num)

def work2():
    global g_num
    print("----in work2, g_num is %d---"%g_num)

print("---Before Thread Creation,g_num is %d---"%g_num)

t1 = Thread(target=work1)
t1.start()
#A delay guarantees t1 Finish in Thread
time.sleep(1)
t2 = Thread(target=work2)
t2.start()

The results are:

---Before Thread Creation,g_num is 100---
----in work1, g_num is 101---
----in work1, g_num is 102---
----in work1, g_num is 103---
----in work2, g_num is 103---

2. Lists are passed to threads as arguments

from threading import Thread
import time

def work1(nums):
    nums.append(44)
    print("----in work1---",nums)

def work2(nums):
    #A delay guarantees t1 Finish in Thread
    time.sleep(1)
    print("----in work2---",nums)

g_nums = [11,22,33]

t1 = Thread(target=work1, args=(g_nums,))
t1.start()

t2 = Thread(target=work2, args=(g_nums,))
t2.start()

The results are:

----in work1--- [11, 22, 33, 44]
----in work2--- [11, 22, 33, 44]

2. Do not share local variables

import threading
from time import sleep

def test(sleepTime):
    num=1
    sleep(sleepTime)
    num+=1
    print('---(%s)--num=%d'%(threading.current_thread(), num))


t1 = threading.Thread(target = test,args=(5,))
t2 = threading.Thread(target = test,args=(1,))

t1.start()
t2.start()

The results are:

---(<Thread(Thread-2, started 12236)>)--num=2
---(<Thread(Thread-1, started 5644)>)--num=2

Summary:

  • Sharing global variables among all threads in a process enables data sharing between multiple threads without using other methods (which is better than multiple processes)
  • The disadvantage is that threads are arbitrary attempts to change global variables, which can cause confusion among multiple threads (i.e., thread insecurity)
  • In multi-threaded development, global variables are data shared by multiple threads, while local variables, etc., are shared by their respective threads and are not shared

 

6. Synchronization

1. Thread Conflict

Assuming that both threads T 1 and t2 increase num=0 by 1, T 1 and t2 modify num 10 times each, the final result of num should be 20.However, due to multithreaded access, the following situations may occur:

When num=0, t1 gets num=0.At this point, the system schedules t1 to a "sleeping" state, converts t2 to a "running" state, and t2 also gets num=0.t2 then adds 1 to the resulting value and assigns num so that num = 1.Then the system schedules t2 as "sleeping" and t1 as "running".Thread t1 then assigns num the 0 it got before plus 1.In this way, Ming Ming T 1 and t2 completed the 1 plus 1 work, but the result is still num=1.

from threading import Thread
import time

g_num = 0

def test1():
    global g_num
    for i in range(1000000):
        g_num += 1

    print("---test1---g_num=%d"%g_num)

def test2():
    global g_num
    for i in range(1000000):
        g_num += 1

    print("---test2---g_num=%d"%g_num)


p1 = Thread(target=test1)
p1.start()
#time.sleep(3) #Run the program again after unblocking, the result will be different

p2 = Thread(target=test2)
p2.start()

print("---g_num=%d---"%g_num)
time.sleep(3)
print("---g_num=%d---"%g_num)

The results are:

---g_num=138526---
---test1---g_num=1264273
---test2---g_num=1374945
---g_num=1374945---

After unblocking, run again as follows:

---test1---g_num=1000000
---g_num=1029220---
---test2---g_num=2000000
---g_num=2000000---

The problem arises because multiple threads do not have control over access to the same resource, causing data corruption, and making the results of running threads unexpected.This phenomenon is called thread insecurity.

2. What is synchronization

  • Synchronization is a collaborative step that runs in a predetermined order.Example: After you finish, I will say again.
  • The word "Tong" can be easily understood as a starting action literally. In fact, it is not, "Tong" should refer to cooperation, assistance and cooperation.
  • If processes and threads are synchronized, it can be understood that the process or threads A and B work together. When A executes to a certain extent, it depends on a result of B, and then stops to indicate that B runs; B executes according to its words, and then gives the result to A;A continues to operate.

3. Solving problems

For the calculation error mentioned above, thread synchronization can be used to solve the problem as follows:

  1. The system calls t1 and gets a value of 0 for num, where the last lock does not allow any other present operation of num
  2. +1 for num value
  3. Unlock, num value is 1, other threads can use num, and num value is not 0 but 1
  4. Similarly, when other threads modify num, they lock it first, then unlock it after processing. No other threads are allowed to access it during the whole process of locking, which ensures the correctness of the data.

 

7. Thread Mutual Exclusion Lock

1. Thread Mutual Exclusion Lock Introduction

When multiple threads modify almost one shared data at the same time, synchronization control is required. Thread synchronization can ensure that multiple threads have secure access to competing resources. The simplest synchronization mechanism is to introduce mutex.

Mutex locks introduce a state to a resource: lock/unlock

When a thread changes shared data, it locks it first, and the state of the resource is "locked", while no other thread can change it; until the thread releases the resource and changes the state of the resource to "unlocked", no other thread can lock the resource again.Mutex locks ensure that only one thread writes at a time, thus ensuring the correctness of data in multithreaded situations.

The threading module defines a Lock class that handles locks easily:

#Create Lock
mutex = threading.Lock()
#locking
mutex.acquire([blocking])
#release
mutex.release()

The lock method acquire can have a blocking parameter.

  • If blocking is set to True, the current thread will be blocked until the lock is acquired (default is True if not specified)
  • If blocking is set to False, the current thread will not be blocked

The code for the example above using mutexes is as follows:

from threading import Thread, Lock
import time

g_num = 0

def test1():
    global g_num
    for i in range(1000000):
        #True Indicates blocking that if the lock is locked before it is locked, the thread will wait here until it is unlocked 
        #False Indicates non-blocking, i.e. no matter the call succeeds in locking, it will not get stuck here,Instead, continue with the following code
        mutexFlag = mutex.acquire(True) 
        if mutexFlag:#Lock up
            g_num += 1
            mutex.release()#Unlock
    print("---test1---g_num=%d"%g_num)

def test2():
    global g_num
    for i in range(1000000):
        mutexFlag = mutex.acquire(True) #True Indicates clogging
        if mutexFlag:#Lock up
            g_num += 1
            mutex.release()#Unlock
    print("---test2---g_num=%d"%g_num)

#Create a mutex
#This defaults to an unlocked state
mutex = Lock()

p1 = Thread(target=test1)
p1.start()


p2 = Thread(target=test2)
p2.start()

time.sleep(5)
print("---g_num=%d---"%g_num)

The results are:

---test1---g_num=1942922
---test2---g_num=2000000
---g_num=2000000---

2. Up-lock and unlock process

  • When a thread invokes the acquire() method of a lock to acquire a lock, the lock enters a locked state.
  • Only one thread at a time can acquire a lock.If another thread tries to acquire the lock at this time, it becomes a "blocked" state, known as "blocked", until the thread that owns the lock calls the release() method to release the lock and the lock enters an "unlocked" state.
  • The thread dispatcher picks one of the threads in the synchronization blocked state to acquire a lock and get the thread into the running state.

summary

Benefits of locks:

  • Ensure that a critical piece of code can only be executed completely from beginning to end by one thread

Disadvantages of locks:

  • Multithreaded concurrent execution is prevented, and a piece of code that contains locks can actually only be executed in a single-threaded mode, which greatly reduces efficiency
  • Since multiple locks can exist, deadlocks can occur when different threads hold different locks and attempt to acquire locks held by the other party

3. Deadlock

When multiple resources are shared between threads, a deadlock can occur if two threads hold a portion of each other's resources and wait for each other's resources at the same time.

Although deadlocks rarely occur, they cause the application to stop responding.Let's look at an example of deadlocks

#coding=utf-8
import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        if mutexA.acquire():
            print(self.name+'----do1---up----')
            time.sleep(1)

            if mutexB.acquire():
                print(self.name+'----do1---down----')
                mutexB.release()
            mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        if mutexB.acquire():
            print(self.name+'----do2---up----')
            time.sleep(1)
            if mutexA.acquire():
                print(self.name+'----do2---down----')
                mutexA.release()
            mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

The results are:

Thread-1----do1---up----
Thread-2----do2---up----

You are now deadlocked

 

 

4. Avoid deadlocks

  • Avoid programming as much as possible (banker algorithm)
  • Add timeout, etc.

 

8. Synchronized Application

from threading import Thread,Lock
from time import sleep

class Task1(Thread):
    def run(self):
        while True:
            if lock1.acquire():
                print("------Task 1 -----")
                sleep(0.5)
                lock2.release()

class Task2(Thread):
    def run(self):
        while True:
            if lock2.acquire():
                print("------Task 2 -----")
                sleep(0.5)
                lock3.release()

class Task3(Thread):
    def run(self):
        while True:
            if lock3.acquire():
                print("------Task 3 -----")
                sleep(0.5)
                lock1.release()

#Use Lock Locks created are not "locked" by default
lock1 = Lock()
#Create another lock and "lock"
lock2 = Lock()
lock2.acquire()
#Create another lock and "lock"
lock3 = Lock()
lock3.acquire()

t1 = Task1()
t2 = Task2()
t3 = Task3()

t1.start()
t2.start()
t3.start()

The results are:

You can use mutexes to accomplish multiple tasks, and an orderly process, which is thread synchronization

 

9. ThreadLocal

In a multithreaded environment, each thread has its own data.A thread is better at using its own local variables than global variables, because local variables are visible only to the thread itself and do not affect other threads, and global variable modifications must be locked.

import threading

# Create Global ThreadLocal object:
local_school = threading.local()

def process_student():
    # Gets the current thread's associated student:
    std = local_school.student
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))

def process_thread(name):
    # binding ThreadLocal Of student:
    local_school.student = name
    process_student()

t1 = threading.Thread(target= process_thread, args=("Se7eN",), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=("HOU",), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

The results are:

Hello, Se7eN (in Thread-A)
Hello, HOU (in Thread-B)

Explain:

  1. The global variable local_school is a ThreadLocal object on which each Thread can read and write student properties without affecting each other.You can think of local_school as a global variable, but each property, such as local_school.student, is a local variable of the thread and can be read and written freely without interrupting each other or managing lock issues, which are handled internally in ThreadLocal.
  2. The most common use of ThreadLocal is to bind a database connection, HTTP requests, user identity information, and so on, for each thread, so that all the processing functions invoked by such a thread can easily access these resources.
  3. A ThreadLocal variable is a global variable, but each thread can only read and write a separate copy of its own thread without interruption.ThreadLocal solves the problem of passing parameters between functions in a thread

 

10. Asynchronous

  • Synchronization call is when you shout your friend to eat, your friend is busy, you have been waiting there until your friend is busy, you go together
  • Asynchronous calls are when you shout to your friend for dinner. Your friend says he knows. When he finishes looking for you, you'll do something else.
def test():
    print("---Processes in the process pool---pid=%d,ppid=%d--"%(os.getpid(),os.getppid()))
    for i in range(3):
        print("----%d---"%i)
        time.sleep(1)
    return "hahah"

def test2(args):
    print("---callback func--pid=%d"%os.getpid())
    print("---callback func--args=%s"%args)
   
pool = Pool(3)
pool.apply_async(func=test,callback=test2)
time.sleep(5)
print("----Main Process-pid=%d----"%os.getpid())

The results are:

--Processes in the process pool--pid=9401,ppid=9400--
----0---
----1---
----2---
---callback func--pid=9400
---callback func--args=hahah
--- Main Process - pid=9400---

Posted by krio on Tue, 30 Apr 2019 15:00:36 -0700