Multithreaded Concurrent Programming

Concurrent programming

  • Concurrent (pseudo): Because of the speed of execution, it is not perceptible
  • Parallel (true): create 10 simultaneous operations

thread

  1. Single-process, single-threaded applications
    • print('666')
  2. What exactly is a thread?What is a process
    • Python does not have this on its own; the threads and processes of the operating system (pseudothreads) called in Python
  3. Multithreaded
    • Minimum unit of work
    • Share all resources in the process
    • Each thread can share a few tasks and ultimately complete the final result

python multithreading principle: python multithreading is actually a pseudo multithreading (multiple threads running on a 1-core CPU, fast switching causes the illusion of simultaneous execution)

Code

import threading
def func(arg):
    print(arg)
    
th = threading.Thread(target=func)
th.start()
print('end')

One application: software

  • The default is that a program has only one process
  • There can be multiple processes (only one by default), and one process can create multiple threads (the default one).

In the case of Python multithreading:

  • Computing intensive operations: inefficient (GIL locks)
  • IO operation: High efficiency

In Python multi-process scenarios:

  • Computing intensive operations: High efficiency (wasted space)
  • IO operations: High efficiency (waste of resources)

When writing Python later:

  • IO-intensive multi-threaded: file/input/output/socket
  • Computing intensive with multiple processes:

process

  • Open up memory independently
  • Data isolation between processes

Note: Processes are designed to provide an environment for threads to work

Threads and processes in Python (GIL locks)

GIL lock, global interpreter lock.Used to limit one thread to be scheduled by the CPU at a time in a process

Extension: Default GIL lock after executing 100 cpu instructions (expiration time).

Use of threads

Multithread Basic Usage

  • Basic examples
import threading
def func(arg):
    print(arg)
    
th = threading.Thread(target=func)
th.start()
print('end')
  • Test example (the main thread executes its own code by default, and then waits for the child thread to finish executing)
import time
import threading

def func(arg):
    time.sleep(arg)
    print(arg)

t1 = threading.Thread(target=func,args=(3,))
t1.start()

t2 = threading.Thread(target=func,args=(9,))
t2.start()

print('end')
  • jojn method:
import time
import threading

def func(arg):
    time.sleep(3)
    print(arg)

print('Start execution t1')
t1 = threading.Thread(target=func,args=(3,))
t1.start()
# No arguments: Let the main thread wait until the child thread t1 finishes executing before executing down
# Arguments: Let the main thread wait here for up to n seconds, and continue down whether execution is complete or not
t1.join(2)

print('Start execution t2')
t2 = threading.Thread(target=func,args=(9,))
t2.start()
t2.join()   # Let the main thread wait until the child thread t1 finishes executing before executing down

print('end')
  • Thread name acquisition
import threading

def func(arg):
    # Gets the object of the thread currently executing the function
    t = threading.current_thread()
    # Gets the current thread name based on the current thread object
    name = t.getName()
    print(name,arg)

t1 = threading.Thread(target=func,args=(3,))
t1.setName('mhy')
t1.start()

t2 = threading.Thread(target=func,args=(9,))
t2.setName('zz')
t2.start()

print('end')
  • Thread nature
# Print 3 or end first?
import threading

def func(arg):
    print(arg)

t1 = threading.Thread(target=func,args=(3,))
# Is start a thread?No
# start tells the cpu that I'm ready and you can schedule me.
t1.start()

print('end')
  • Object-oriented submultithreaded use
import threading
class MyThread(threading.Thread):
    def run(self):
        print(123,self._args,self._kwargs)


t1 = MyThread(args=(11,))
t1.start()

t2 = MyThread(args=(12,))
t2.start()
  • Computing-intensive multithreading (less useful)
import threading
def func(lis,num):
    et = [i+num for i in lis]
    print(et)

t1 = threading.Thread(target=func,args=([11,22,33],1))
t1.start()

t2 = threading.Thread(target=func,args=([44,55,66],100))
t2.start()
  • Instance Code
import threading
import time

#Definition class inherits Thread thread
class CodingThread(threading.Thread):
    def run(self):
        for x in range(3):
            print('Writing code%s' % threading.current_thread())
            time.sleep(1)

class DrawingThread(threading.Thread):
    def run(self):
        for x in range(3):
            print('Drawing%s' % threading.current_thread())
            time.sleep(1)

def main():
    t1 = CodingThread()
    t2 = DrawingThread()

    t1.start()
    t2.start()

if __name__ == '__main__':
    main()

Thread Security (Lock)

  • Thread-safe, internal queuing for all threads when multithreaded, such as list/dict/Queue
  • Thread Insecurity + People (LOCK) = Queue Processing

Introduction:

The interpreter that comes with Python is CPython.The multithreading of the CPython interpreter is actually a pseudo multithreading (in a multicore CPU, only one core can be used, not multicore).There is only one thread executing at the same time. To ensure that only one thread is executing at the same time, there is something called a GIL (Global Intepreter Lock) in the CPython interpreter, called a global interpreter lock.This interpreter lock is necessary.Because memory management for the CPython interpreter is not thread safe.Of course, there are other interpreters besides the CPython interpreter, some of which do not have GIL locks. See below:

  1. Jython: A Python interpreter implemented in Java.There is no GIL lock.
  2. IronPython: A Python interpreter implemented with.net.There is no GIL lock.
  3. PyPy: A Python interpreter implemented in Python.There is a GIL lock.

The GIL is a pseudo multithreaded one.However, some IO operations, such as file reading and writing and network requests, can greatly improve efficiency.Multithreading is recommended for IO operations to improve efficiency.Multithreading is not recommended for some CPU computation operations, but multiprocessing is recommended.

Insecure examples

import time
import threading

lit = []
def func(arg):
    lit.append(arg)
    time.sleep(0.04)
    m = lit[-1]
    print(arg,m)  # m and arg should be a value

for num in range(10):
    t1 = threading.Thread(target=func,args=(num,))
    t1.start()

Lock mechanism principle

Multiple threads execute simultaneously, which results in simultaneous execution of data and inconsistent results.

When a thread gets execute right first, it will lock the execution process, other threads can't use it. It must wait until the thread finishes execution before other threads can get execute right. Executing threads will solve the problem of data inconsistency

Lock Lock (one at a time)

import time
import threading

lit = []
lock = threading.Lock()  # Create a lock

def func(arg):
    lock.acquire() # Lock the line of code below until release is encountered
    lit.append(arg)
    time.sleep(0.04)
    m = lit[-1]
    print(arg,m)
    lock.release()  # Release lock

for num in range(10):
    t1 = threading.Thread(target=func,args=(num,))
    t1.start()

RLock recursive locks (release more than one at a time),

Because lock locks can cause deadlocks if they have a multilevel lock mechanism, they have Rlock (recursive lock)

The code is as follows:

import time
import threading

lit = []
lock = threading.RLock()  # Create a recursive lock

def func(arg):
    # Lock can only unlock one lock, causing deadlock threads
    # RLock can unlock both locks, resolving the following issues
    lock.acquire() # Lock the line of code below until release is encountered
    lock.acquire()
    lit.append(arg)
    time.sleep(0.04)
    m = lit[-1]
    print(arg,m)
    lock.release()  # Release lock
    lock.release()  # Release lock

for num in range(10):
    t1 = threading.Thread(target=func,args=(num,))
    t1.start()

BoundedSemaphore (N at a time) semaphores

import time
import threading

# Create a lock that allows you to lock several times at a time, defaulting to one
lock = threading.BoundedSemaphore(3)

def func(arg):
    lock.acquire() 
    print(arg)
    time.sleep(1)
    lock.release()

for num in range(20):
    t = threading.Thread(target=func,args=(num,))
    t.start()

Condition (x playback times) dynamic input

The Lock version of the Producer and Consumer model works fine, but there is a disadvantage. Among consumers, there is always a While True Dead Loop and Lock to determine that money is not enough. Locking is a CPU-intensive activity, so it is not the best way to do this, and a better way isUsing Threading.conditioning, threading.conditioning can be blocked waiting when there is no data, and notify-related functions can be used to notify other threads that are waiting once appropriate data is available.This eliminates the need for some useless locking and unlocking operations.Can improve program performance.First, the threading.Condition-related functions are introduced. Theading.Condition, like threading.Lock, can be locked when modifying global data or unlocked after modification.Here is a brief introduction to some of the commonly used functions:

  1. acquire: lock
  2. release: unlock
  3. Wait: Leaves the current thread waiting and releases the lock.It can be waked up by other threads using the notify and notify_all functions.Continue to wait for lock after waking up, and execute the following code after lock.
  4. notify: notifies a waiting thread, defaulting to the first waiting thread.
  5. Notify_all: Notifies the waiting thread.Notfy and notify_all do not release locks.And needs to be called before release.
# Method One
import time
import threading

# Create a lock that allows you to lock several times at a time, defaulting to one
lock = threading.Condition()

def func(arg):
    print('Threads come in')
    lock.acquire()
    lock.wait()

    print(arg)
    time.sleep(1)
    lock.release()

for num in range(3):
    t = threading.Thread(target=func,args=(num,))
    t.start()

while True:
    num = int(input('>>>:'))
    lock.acquire()
    lock.notify(num)
    lock.release()
    
# Method 2
import time
import threading

# Create a lock that allows you to lock several times at a time, defaulting to one
lock = threading.Condition()

def xxx():
    print('Execute the function')
    input('>>>:')
    return True

def func(arg):
    print('Threads come in')
    lock.wait_for(xxx)
    print(arg)
    time.sleep(1)

for num in range(3):
    t = threading.Thread(target=func,args=(num,))
    t.start()

Event (event) put all at once

import threading

# Create a lock that allows you to lock several times at a time, defaulting to one
lock = threading.Event()

def func(arg):
    print('Threads come in')
    lock.wait()  # Red light
    print(arg)

for num in range(10):
    t = threading.Thread(target=func,args=(num,))
    t.start()

input('>>>')
lock.set()   # Green light
input('>>>')

# Return to red light
lock.clear()
for num in range(10):
    t = threading.Thread(target=func,args=(num,))
    t.start()

input('>>>')
lock.set()   # Green light
input('>>>')

Thread Summary

Thread Safety: Lists and dictionaries are thread safe;

Why lock?

  • Non-Thread Security
  • When controlling a piece of code, you can only execute at most a few simultaneous executions at a time

threading.local

Create a dictionary key-value pair for each thread to isolate data

Sample code:

import time
import threading

pond = threading.local()

def func(arg):
    # Internally, a space is created for the current thread to store, phone = its own value, isolating the data
    pond.phone = arg 
    time.sleep(2)
    # Value current thread's own space
    print(pond.phone,arg)  

for num in range(10):
    t = threading.Thread(target=func,args=(num,))
    t.start()

Thread Pool

Use concurrent to create thread pools. Thread pools can effectively solve the endless problem of threads. Users create a thread with every request. Thread pools can help us solve this problem. Thread pools can be set, at most simultaneous.Line n threads, set by yourself.

Sample code:

import time
from concurrent.futures import ThreadPoolExecutor

# Create a thread pool (up to 5 threads executing simultaneously)
pool = ThreadPoolExecutor(5)

def func(arg1,arg2):
    time.sleep(1)
    print(arg1,arg2)

for num in range(5):
    # De-thread pool requests a thread to execute func functions
    pool.submit(func,num,8)

Queue Thread Security Queue:

Locking is a frequent process when accessing global variables in a thread.If you want to store some data in a queue, Python has a built-in thread-safe module called the queue module.The queue module in Python provides synchronous, thread-safe queue classes, including FIFO (first in, first out) queue Queue and LIFO (last in, first out) queue LifoQueue.These queues implement the lock primitive, which can be interpreted as an atomic operation, either not doing it or doing it all, and can be used directly in multiple threads.Queues can be used to synchronize threads.The related functions are as follows:

  1. Initialize Queue(maxsize): Create a first-in-first-out queue.
  2. qsize(): Returns the size of the queue.
  3. empty(): Determines if the queue is empty.
  4. full(): Determine if the queue is full.
  5. get(): Take the last data from the queue.
  6. put(): Put a data into a queue.

Sample Code

 #encoding:utf-8

from queue import Queue
import threading
import time

# q = Queue(4)  # Add 4 Queues
# q.put(10)   #The first queue inserts a piece of data
# q.put(4)    #The second queue inserts a piece of data

# for x in range(4):
#     q.put(x)
#
# for x in range(4):
#     print(q.get())
# print(q.empty())
# print(q.full())
# print(q.qsize())

def set_value(q):
    index = 0
    while True:
        q.put(index)
        index +=1
        time.sleep(2)
def get_value(q):
    while True:
        print(q.get())

def main():
    q = Queue(4)
    t1 = threading.Thread(target=set_value,args=[q])
    t2 = threading.Thread(target=get_value,args=[q])

    t1.start()
    t2.start()

if __name__ == '__main__':
    main()

Producer-consumer model

Model Three Parts

  • Producer
    • Queue: FIFO
    • Stack: LIFO
  • Consumer
  • queue

The producer and consumer models solve the problem of not waiting all the time

Create Queue

Sample code:

import time
import threading
from queue import Queue

q = Queue()

def producer(id):
    '''Producer'''
    while True:
        time.sleep(2)
        q.put('Steamed stuffed bun')
        print('cook %s A bun was made'%id)

def consumer(id):
    '''Consumer'''
    while True:
        time.sleep(1)
        v1 = q.get()
        print('customer %s I ate a bun'%id)

for produce in range(1,4):
    t1 = threading.Thread(target=producer,args=(produce,))
    t1.start()

for consu in range(1,3):
    t2 = threading.Thread(target=consumer,args=(consu,))
    t2.start()

Summary:

  1. Operating Systems Help Developers Operate Hardware
  2. Programmers write code to run on the operating system (relying on interpreters)
  3. There are especially many tasks.Previous execution, (serial), can now use multithreading

Why create threads?

  • Since threads are the smallest unit of cpu work, creating threads can take advantage of multicore advantages to implement parallel operations (java,C#)

Why create a process?

  • Data isolation between processes (java/C)

Python

  • There is a GIL lock in Python.
    • Cause: Multi-threading cannot take advantage of multi-core advantages
    • Solution: Multiprocessing (wasting resources)
    • Summary:
      • IO intensive: multi-threaded
      • Computing intensive: multi-process
  • Thread Creation
    • Thread
    • Object-oriented inheritance (Threading.Thread)
  • Other
    • jojn
    • setDeanon
    • setName
    • threading.current_thread()
  • lock
    • Get
    • release

Keywords: Python Java Programming socket

Added by razmon on Thu, 12 Sep 2019 20:39:14 +0300