python并发编程(2)

简介: python并发编程(2)

6.单例模式(会手写)

面向对象 + 多线程相关的一个面试题(以后项目和源码中会用到)。

之前写一个类,每次执行 类() 都会实例化一个类的对象。

class Foo:
    pass
obj1 = Foo()
obj2 = Foo()
print(obj1,obj2)

以后开发会遇到单例模式,每次实例化类的对象时,都是最开始创建的那个对象,不再重复创建对象。

  • 简单的实现单例模式
class Singleton:
    instance = None
    def __init__(self, name):
        self.name = name
    def __new__(cls, *args, **kwargs):
        # 返回空对象
        if cls.instance:
            return cls.instance
        cls.instance = object.__new__(cls)
        return cls.instance
obj1 = Singleton('alex')
obj2 = Singleton('SB')
print(obj1,obj2)
  • 多线程执行单例模式,有BUG
import threading
import time
class Singleton:
    instance = None
    def __init__(self, name):
        self.name = name
    def __new__(cls, *args, **kwargs):
        if cls.instance:
            return cls.instance
        time.sleep(0.1)
        cls.instance = object.__new__(cls)
        return cls.instance
def task():
    obj = Singleton('x')
    print(obj)
for i in range(10):
    t = threading.Thread(target=task)
    t.start()
  • 加锁解决BUG
import threading
import time
class Singleton:
    instance = None
    lock = threading.RLock()
    def __init__(self, name):
        self.name = name
    def __new__(cls, *args, **kwargs):
        with cls.lock:
            if cls.instance:
                return cls.instance
            time.sleep(0.1)
            cls.instance = object.__new__(cls)
        return cls.instance
def task():
    obj = Singleton('x')
    print(obj)
for i in range(10):
    t = threading.Thread(target=task)
    t.start()
  • 加判断,提升性能
import threading
import time
class Singleton:
    instance = None
    lock = threading.RLock()
    def __init__(self, name):
        self.name = name
    def __new__(cls, *args, **kwargs):
        if cls.instance:
            return cls.instance
        with cls.lock:
            if cls.instance:
                return cls.instance
            time.sleep(0.1)
            cls.instance = object.__new__(cls)
        return cls.instance
def task():
    obj = Singleton('x')
    print(obj)
for i in range(10):
    t = threading.Thread(target=task)
    t.start()
# 执行1000行代码
data = Singleton('asdfasdf')
print(data)

总结

  1. 进程和线程的区别和应用场景。
  2. 什么是GIL锁
  3. 多线程和线程池的使用。
  4. 线程安全 & 线程锁 & 死锁
  5. 单例模式

进程

1. 多进程开发

进程是计算机中资源分配的最小单元;一个进程中可以有多个线程,同一个进程中的线程共享资源;

进程与进程之间则是相互隔离。如QQ与微信。

Python中通过多进程可以利用CPU的多核优势,计算密集型操作适用于多进程。

1.1 进程介绍

import multiprocessing
def task():
  pass
if __name__ == '__main__':
    p1 = multiprocessing.Process(target=task)
    p1.start()
from multiprocessing import Process
def task(arg):
  pass
def run():
    p = multiprocessing.Process(target=task, args=('xxx',))
    p.start()
if __name__ == '__main__':
    run()

关于在Python中基于multiprocessiong模块操作的进程:


Depending on the platform, multiprocessing supports three ways to start a process. These start methods are


fork,【“拷贝”几乎所有资源】【支持文件对象/线程锁等传参】【unix】【任意位置开始】【快】


The parent process uses os.fork() to fork the Python interpreter. The child process, when it begins, is effectively identical to the parent process. All resources of the parent are inherited by the child process. Note that safely forking a multithreaded process is problematic.Available on Unix only. The default on Unix.


spawn,【run参数传必备资源】【不支持文件对象/线程锁等传参】【unix、win】【main代码块开始】【慢】


The parent process starts a fresh python interpreter process. The child process will only inherit those resources necessary to run the process object’s run() method. In particular, unnecessary file descriptors and handles from the parent process will not be inherited. Starting a process using this method is rather slow compared to using fork or forkserver.Available on Unix and Windows. The default on Windows and macOS.


forkserver,【run参数传必备资源】【不支持文件对象/线程锁等传参】【部分unix】【main代码块开始】


When the program starts and selects the forkserver start method, a server process is started. From then on, whenever a new process is needed, the parent process connects to the server and requests that it fork a new process. The fork server process is single threaded so it is safe for it to use os.fork(). No unnecessary resources are inherited.Available on Unix platforms which support passing file descriptors over Unix pipes.


import multiprocessing

multiprocessing.set_start_method("spawn")(前提是操作系统支持这样)


Changed in version 3.8: On macOS, the spawn start method is now the default. The fork start method should be considered unsafe as it can lead to crashes of the subprocess. See bpo-33725.


Changed in version 3.4: spawn added on all unix platforms, and forkserver added for some unix platforms. Child processes no longer inherit all of the parents inheritable handles on Windows.


On Unix using the spawn or forkserver start methods will also start a resource tracker process which tracks the unlinked named system resources (such as named semaphores or SharedMemory objects) created by processes of the program. When all processes have exited the resource tracker unlinks any remaining tracked object. Usually there should be none, but if a process was killed by a signal there may be some “leaked” resources. (Neither leaked semaphores nor shared memory segments will be automatically unlinked until the next reboot. This is problematic for both objects because the system allows only a limited number of named semaphores, and shared memory segments occupy some space in the main memory.)


官方文档:multiprocessing — Process-based parallelism — Python 3.10.0 documentation

  • 示例1
import multiprocessing
import time
"""
def task():
    print(name)
    name.append(123)
if __name__ == '__main__':
    multiprocessing.set_start_method("fork")  # fork、spawn、forkserver
    name = []
    p1 = multiprocessing.Process(target=task)
    p1.start()
    time.sleep(2)
    print(name)  # []
"""
"""
def task():
    print(name) # [123]
if __name__ == '__main__':
    multiprocessing.set_start_method("fork")  # fork、spawn、forkserver
    name = []
    name.append(123)
    p1 = multiprocessing.Process(target=task)
    p1.start()
"""
"""
def task():
    print(name)  # []
if __name__ == '__main__':
    multiprocessing.set_start_method("fork")  # fork、spawn、forkserver
    name = []
    p1 = multiprocessing.Process(target=task)
    p1.start()
    name.append(123)
"""
  • 示例2
import multiprocessing
def task():
    print(name)
    print(file_object)
if __name__ == '__main__':
    multiprocessing.set_start_method("fork")  # fork、spawn、forkserver
    name = []
    file_object = open('x1.txt', mode='a+', encoding='utf-8')
    p1 = multiprocessing.Process(target=task)
    p1.start()

案例:

import multiprocessing
def task():
    print(name)
    file_object.write("alex\n")
    file_object.flush()
if __name__ == '__main__':
    multiprocessing.set_start_method("fork")
    name = []
    file_object = open('x1.txt', mode='a+', encoding='utf-8')
    file_object.write("卢本伟\n")
    p1 = multiprocessing.Process(target=task)
    p1.start()
'''
卢本伟
alex
卢本伟
'''
import multiprocessing
import threading
import time
def func():
    print("来了")
    with lock:
        print(666)
        time.sleep(1)
def task():
    # 拷贝的锁也是被申请走的状态
    # 被谁申请走了? 被子进程中的主线程申请走了
    for i in range(10):
        t = threading.Thread(target=func)
        t.start()
    time.sleep(2)
    lock.release()
if __name__ == '__main__':
    multiprocessing.set_start_method("fork")
    name = []
    lock = threading.RLock()
    lock.acquire()
    p1 = multiprocessing.Process(target=task)
    p1.start()

1.2 常见功能

进程的常见方法:

  • p.start(),当前进程准备就绪,等待被CPU调度(工作单元其实是进程中的线程)。
  • p.join(),等待当前进程的任务执行完毕后再向下继续执行。
import time
from multiprocessing import Process
def task(arg):
    time.sleep(2)
    print("执行中...")
if __name__ == '__main__':
    multiprocessing.set_start_method("spawn")
    p = Process(target=task, args=('xxx',))
    p.start()
    p.join()
    print("继续执行...")

p.daemon = 布尔值,守护进程(必须放在start之前)

  • p.daemon =True,设置为守护进程,主进程执行完毕后,子进程也自动关闭。
  • p.daemon =False,设置为非守护进程,主进程等待子进程,子进程执行完毕后,主进程才结束。
import time
from multiprocessing import Process
def task(arg):
    time.sleep(2)
    print("执行中...")
if __name__ == '__main__':
    multiprocessing.set_start_method("spawn")
    p = Process(target=task, args=('xxx',))
    p.daemon = True
    p.start()
    print("继续执行...")
  • 进程的名称的设置和获取
import os
import time
import threading
import multiprocessing
def func():
    time.sleep(3)
def task(arg):
    for i in range(10):
        t = threading.Thread(target=func)
        t.start()
    print(os.getpid(), os.getppid())
    print("线程个数", len(threading.enumerate()))
    time.sleep(2)
    print("当前进程的名称:", multiprocessing.current_process().name)
if __name__ == '__main__':
    print(os.getpid())
    multiprocessing.set_start_method("spawn")
    p = multiprocessing.Process(target=task, args=('xxx',))
    p.name = "哈哈哈哈"
    p.start()
    print("继续执行...")
  • 自定义进程类,直接将线程需要做的事写到run方法中。
import multiprocessing
class MyProcess(multiprocessing.Process):
    def run(self):
        print('执行此进程', self._args)
if __name__ == '__main__':
    multiprocessing.set_start_method("spawn")
    p = MyProcess(args=('xxx',))
    p.start()
    print("继续执行...")

CPU个数,程序一般创建多少个进程?(利用CPU多核优势)。

import multiprocessing
multiprocessing.cpu_count()
import multiprocessing
if __name__ == '__main__':
    count = multiprocessing.cpu_count()
    for i in range(count - 1):
        p = multiprocessing.Process(target=xxxx)
        p.start()

2.进程间数据的共享

进程是资源分配的最小单元,每个进程中都维护自己独立的数据,不共享。

import multiprocessing
def task(data):
    data.append(666)
if __name__ == '__main__':
    data_list = []
    p = multiprocessing.Process(target=task, args=(data_list,))
    p.start()
    p.join()
    print("主进程:", data_list) # []

注:线程这样操作可以[666]而进程不可以,线程data_list与data指向同一块地址,而进程则不。

如果想要让他们之间进行共享,则可以借助一些特殊的东西来实现。

2.1 共享

Shared memory

Data can be stored in a shared memory map using Value or Array. For example, the following code

 'c': ctypes.c_char,  'u': ctypes.c_wchar,
    'b': ctypes.c_byte,  'B': ctypes.c_ubyte, 
    'h': ctypes.c_short, 'H': ctypes.c_ushort,
    'i': ctypes.c_int,   'I': ctypes.c_uint,  (其u表示无符号)
    'l': ctypes.c_long,  'L': ctypes.c_ulong, 
    'f': ctypes.c_float, 'd': ctypes.c_double
from multiprocessing import Process, Value, Array
def func(n, m1, m2):
    n.value = 888
    m1.value = 'a'.encode('utf-8')
    m2.value = "武"
if __name__ == '__main__':
    num = Value('i', 666)
    v1 = Value('c')
    v2 = Value('u')
    p = Process(target=func, args=(num, v1, v2))
    p.start()
    p.join()
    print(num.value)  # 888
    print(v1.value)  # a
    print(v2.value)  # 武
from multiprocessing import Process, Value, Array
def f(data_array):
    data_array[0] = 666
if __name__ == '__main__':
    arr = Array('i', [11, 22, 33, 44]) # 数组:元素类型必须是int; 只能是这么几个数据。
    p = Process(target=f, args=(arr,))
    p.start()
    p.join()
    print(arr[:])

Server process


A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

from multiprocessing import Process, Manager
def f(d, l):
    d[1] = '1'
    d['2'] = 2
    d[0.25] = None
    l.append(666)
if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        l = manager.list()
        p = Process(target=f, args=(d, l))
        p.start()
        p.join()
        print(d)
        print(l)

2.2 交换

multiprocessing supports two types of communication channel between processes

Queues

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

The Queue class is a near clone of queue.Queue. For example

import multiprocessing
def task(q):
    for i in range(10):
        q.put(i)
if __name__ == '__main__':
    queue = multiprocessing.Queue()
    p = multiprocessing.Process(target=task, args=(queue,))
    p.start()
    p.join()
    print("主进程")
    print(queue.get())
    print(queue.get())
    print(queue.get())
    print(queue.get())
    print(queue.get())

Pipes

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

The Pipe() function returns a pair of connection objects connected by a pipe which by default is duplex (two-way). For example:

import time
import multiprocessing
def task(conn):
    time.sleep(1)
    conn.send([111, 22, 33, 44])
    data = conn.recv() # 阻塞
    print("子进程接收:", data)
    time.sleep(2)
if __name__ == '__main__':
    parent_conn, child_conn = multiprocessing.Pipe()
    p = multiprocessing.Process(target=task, args=(child_conn,))
    p.start()
    info = parent_conn.recv() # 阻塞
    print("主进程接收:", info)
    parent_conn.send(666)

上述都是Python内部提供的进程之间数据共享和交换的机制,作为了解即可,在项目开发中很少使用,后期项目中一般会借助第三方的来做资源的共享,例如:MySQL、redis等。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

3. 进程锁

如果多个进程抢占式去做某些操作时候,为了防止操作出问题,可以通过进程锁来避免。

import time
from multiprocessing import Process, Value, Array
def func(n, ):
    n.value = n.value + 1
if __name__ == '__main__':
    num = Value('i', 0)
    for i in range(20):
        p = Process(target=func, args=(num,))
        p.start()
    time.sleep(3)
    print(num.value)
import time
from multiprocessing import Process, Manager
def f(d, ):
    d[1] += 1
if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        d[1] = 0
        for i in range(20):
            p = Process(target=f, args=(d,))
            p.start()
        time.sleep(3)
        print(d)
import time
import multiprocessing
def task():
    # 假设文件中保存的内容就是一个值:10
    with open('f1.txt', mode='r', encoding='utf-8') as f:
        current_num = int(f.read())
    print("排队抢票了")
    time.sleep(1)
    current_num -= 1
    with open('f1.txt', mode='w', encoding='utf-8') as f:
        f.write(str(current_num))
if __name__ == '__main__':
    for i in range(20):
        p = multiprocessing.Process(target=task)
        p.start()

很显然,多进程在操作时就会出问题,此时就需要锁来介入:

import time
import multiprocessing
def task(lock):
    print("开始")
    lock.acquire()
    # 假设文件中保存的内容就是一个值:10
    with open('f1.txt', mode='r', encoding='utf-8') as f:
        current_num = int(f.read())
    print("排队抢票了")
    time.sleep(0.5)
    current_num -= 1
    with open('f1.txt', mode='w', encoding='utf-8') as f:
        f.write(str(current_num))
    lock.release()
if __name__ == '__main__':
    multiprocessing.set_start_method("spawn")
    lock = multiprocessing.RLock() # 进程锁
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(lock,))
        p.start()
    # spawn模式,需要特殊处理。
    time.sleep(7)
import time
import multiprocessing
import os
def task(lock):
    print("开始")
    lock.acquire()
    # 假设文件中保存的内容就是一个值:10
    with open('f1.txt', mode='r', encoding='utf-8') as f:
        current_num = int(f.read())
    print(os.getpid(), "排队抢票了")
    time.sleep(0.5)
    current_num -= 1
    with open('f1.txt', mode='w', encoding='utf-8') as f:
        f.write(str(current_num))
    lock.release()
if __name__ == '__main__':
    multiprocessing.set_start_method("spawn")
    lock = multiprocessing.RLock()
    process_list = []
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(lock,))
        p.start()
        process_list.append(p)
    # spawn模式,需要特殊处理。
    for item in process_list:
        item.join()
import time
import multiprocessing
def task(lock):
    print("开始")
    lock.acquire()
    # 假设文件中保存的内容就是一个值:10
    with open('f1.txt', mode='r', encoding='utf-8') as f:
        current_num = int(f.read())
    print("排队抢票了")
    time.sleep(1)
    current_num -= 1
    with open('f1.txt', mode='w', encoding='utf-8') as f:
        f.write(str(current_num))
    lock.release()
if __name__ == '__main__':
    multiprocessing.set_start_method('fork')
    lock = multiprocessing.RLock()
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(lock,))
        p.start()

4. 进程池

import time
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
def task(num):
    print("执行", num)
    time.sleep(2)
if __name__ == '__main__':
    # 修改模式
    pool = ProcessPoolExecutor(4)
    for i in range(10):
        pool.submit(task, i)
  print(1)
  print(2)
import time
from concurrent.futures import ProcessPoolExecutor
def task(num):
    print("执行", num)
    time.sleep(2)
if __name__ == '__main__':
    pool = ProcessPoolExecutor(4)
    for i in range(10):
        pool.submit(task, i)
  # 等待进程池中的任务都执行完毕后,再继续往后执行。
    pool.shutdown(True)
    print(1)
import time
from concurrent.futures import ProcessPoolExecutor
import multiprocessing
def task(num):
    print("执行", num)
    time.sleep(2)
    return num
def done(res):
    print(multiprocessing.current_process())
    time.sleep(1)
    print(res.result())
    time.sleep(1)
if __name__ == '__main__':
    pool = ProcessPoolExecutor(4)
    for i in range(50):
        fur = pool.submit(task, i)
        fur.add_done_callback(done) # done的调用由主进程处理(与线程池不同)
    print(multiprocessing.current_process())
    pool.shutdown(True)

注意:如果在进程池中要使用进程锁,则需要基于Manager中的Lock和RLock来实现。

import time
import multiprocessing
from concurrent.futures.process import ProcessPoolExecutor
def task(lock):
    print("开始")
    # lock.acquire()
    # lock.relase()
    with lock:
        # 假设文件中保存的内容就是一个值:10
        with open('f1.txt', mode='r', encoding='utf-8') as f:
            current_num = int(f.read())
        print("排队抢票了")
        time.sleep(1)
        current_num -= 1
        with open('f1.txt', mode='w', encoding='utf-8') as f:
            f.write(str(current_num))
if __name__ == '__main__':
    pool = ProcessPoolExecutor()
    # lock_object = multiprocessing.RLock() # 不能使用
    manager = multiprocessing.Manager()
    lock_object = manager.RLock() # Lock
    for i in range(10):
        pool.submit(task, lock_object)

案例:计算每天用户访问情况。

image.png

示例1

import os
import time
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Manager
def task(file_name, count_dict):
    ip_set = set()
    total_count = 0
    ip_count = 0
    file_path = os.path.join("files", file_name)
    file_object = open(file_path, mode='r', encoding='utf-8')
    for line in file_object:
        if not line.strip():
            continue
        user_ip = line.split(" - -", maxsplit=1)[0].split(",")[0]
        total_count += 1
        if user_ip in ip_set:
            continue
        ip_count += 1
        ip_set.add(user_ip)
    count_dict[file_name] = {"total": total_count, 'ip': ip_count}
    time.sleep(1)
def run():
    # 根据目录读取文件并初始化字典
    """
        1.读取目录下所有的文件,每个进程处理一个文件。
    """
    pool = ProcessPoolExecutor(4)
    with Manager() as manager:
        """
        count_dict={
          "20210322.log":{"total":10000,'ip':800},
        }
        """
        count_dict = manager.dict()
        for file_name in os.listdir("files"):
            pool.submit(task, file_name, count_dict)
        pool.shutdown(True)
        for k, v in count_dict.items():
            print(k, v)
if __name__ == '__main__':
    run()

示例2

import os
import time
from concurrent.futures import ProcessPoolExecutor
def task(file_name):
    ip_set = set()
    total_count = 0
    ip_count = 0
    file_path = os.path.join("files", file_name)
    file_object = open(file_path, mode='r', encoding='utf-8')
    for line in file_object:
        if not line.strip():
            continue
        user_ip = line.split(" - -", maxsplit=1)[0].split(",")[0]
        total_count += 1
        if user_ip in ip_set:
            continue
        ip_count += 1
        ip_set.add(user_ip)
    time.sleep(1)
    return {"total": total_count, 'ip': ip_count}
def outer(info, file_name):
    def done(res, *args, **kwargs):
        info[file_name] = res.result()
    return done
def run():
    # 根据目录读取文件并初始化字典
    """
        1.读取目录下所有的文件,每个进程处理一个文件。
    """
    info = {}
    pool = ProcessPoolExecutor(4)
    for file_name in os.listdir("files"):
        fur = pool.submit(task, file_name)
        fur.add_done_callback(  outer(info, file_name)  ) # 回调函数:主进程
    pool.shutdown(True)
    for k, v in info.items():
        print(k, v)
if __name__ == '__main__':
    run()

5. 协程

暂时以了解为主。


计算机中提供了:线程、进程 用于实现并发编程(真实存在)。


协程(Coroutine),是程序员通过代码搞出来的一个东西(非真实存在)。


协程也可以被称为微线程,是一种用户态内的上下文切换技术。

简而言之,其实就是通过一个线程实现代码块相互切换执行(来回跳着执行)。

例如:

def func1():
    print(1)
    ...
    print(2)
def func2():
    print(3)
    ...
    print(4)
func1()
func2()

上述代码是普通的函数定义和执行,按流程分别执行两个函数中的代码,并先后会输出:1、2、3、4

但如果介入协程技术那么就可以实现函数见代码切换执行,最终输入:1、3、2、4

在Python中有多种方式可以实现协程,例如:

greenlet

pip install greenlet
from greenlet import greenlet
def func1():
    print(1)        # 第1步:输出 1
    gr2.switch()    # 第3步:切换到 func2 函数
    print(2)        # 第6步:输出 2
    gr2.switch()    # 第7步:切换到 func2 函数,从上一次执行的位置继续向后执行
def func2():
    print(3)        # 第4步:输出 3
    gr1.switch()    # 第5步:切换到 func1 函数,从上一次执行的位置继续向后执行
    print(4)        # 第8步:输出 4
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() # 第1步:去执行 func1 函数

yield

def func1():
    yield 1
    yield from func2()
    yield 2
def func2():
    yield 3
    yield 4
f1 = func1()
for item in f1:
    print(item)

虽然上述两种都实现了协程,但这种编写代码的方式没啥意义。

这种来回切换执行,可能反倒让程序的执行速度更慢了(相比较于串行)。

协程如何才能更有意义呢?


不要让用户手动去切换,而是遇到IO操作时能自动切换。

Python在3.4之后推出了asyncio模块 + Python3.5推出async、async语法 ,内部基于协程并且遇到IO请求自动化切换。

import asyncio
async def func1():
    print(1)
    await asyncio.sleep(2)
    print(2)
async def func2():
    print(3)
    await asyncio.sleep(2)
    print(4)
tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
"""
需要先安装:pip3 install aiohttp
"""
import aiohttp
import asyncio
async def fetch(session, url):
    print("发送请求:", url)
    async with session.get(url, verify_ssl=False) as response:
        content = await response.content.read()
        file_name = url.rsplit('_')[-1]
        with open(file_name, mode='wb') as file_object:
            file_object.write(content)
async def main():
    async with aiohttp.ClientSession() as session:
        url_list = [
            'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
            'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
            'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
        ]
        tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
        await asyncio.wait(tasks)
if __name__ == '__main__':
    asyncio.run(main())

通过上述内容发现,在处理IO请求时,协程通过一个线程就可以实现并发的操作。

协程、线程、进程的区别?

线程,是计算机中可以被cpu调度的最小单元。

进程,是计算机资源分配的最小单元(进程为线程提供资源)。

一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。


由于CPython中GIL的存在:

   - 线程,适用于IO密集型操作。

   - 进程,适用于计算密集型操作。


协程,协程也可以被称为微线程,是一种用户态内的上下文切换技术,在开发中结合遇到IO自动切换,就可以通过一个线程实现并发操作。



所以,在处理IO操作时,协程比线程更加节省开销(协程的开发难度大一些)。


现在很多Python中的框架都在支持协程,比如:FastAPI、Tornado、Sanic、Django 3、aiohttp等,企业开发使用的也越来越多(目前不是特别多)。


关于协程,目前同学们先了解这些概念即可,更深入的开发、应用 暂时不必过多了解,等大家学了Web框架和爬虫相关知识之后,再来学习和补充效果更佳。有兴趣想要研究的同学可以沛齐老师写的文章和专题视频:


文章


https://pythonav.com/wiki/detail/6/91/

https://zhuanlan.zhihu.com/p/137057192


视频


asyncio到底是个啥?【python async await】_哔哩哔哩_bilibili

总结

  1. 了解的进程的几种模式
  2. 掌握进程和进程池的常见操作
  3. 进程之间数据共享
  4. 进程锁
  5. 协程、进程、线程的区别(概念)
相关文章
|
3月前
|
安全 Python
Python并发编程必备技能:掌握threading模块,让你的代码跑得更快!
【8月更文挑战第22天】Python并发编程采用多线程技术实现任务的同时执行。利用`threading`模块可轻松管理和创建线程。通过`Thread`类实例化线程并用`start()`方法启动。线程同步通过`Lock`确保资源访问互斥,或用`Semaphore`控制并发数量。线程间通信则可通过`Queue`安全传递数据,实现生产者-消费者模式等功能。这些工具有效避免了竞态条件,确保了程序的正确性和效率。
60 1
|
1月前
|
并行计算 数据处理 Python
Python并发编程迷雾:IO密集型为何偏爱异步?CPU密集型又该如何应对?
在Python的并发编程世界中,没有万能的解决方案,只有最适合特定场景的方法。希望本文能够为你拨开迷雾,找到那条通往高效并发编程的光明大道。
42 2
|
12天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
1月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
31 3
|
1月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
深入探索:Python中的并发编程新纪元——协程与异步函数解析
27 3
|
1月前
|
数据采集 数据处理 调度
探索Python的并发编程
本文深入探讨Python中的并发编程,包括线程、进程和异步I/O。通过实例展示如何有效利用这些工具提升程序性能,并讨论在应用中需注意的问题及最佳实践。
|
2月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
在Python异步编程领域,协程与异步函数成为处理并发任务的关键工具。协程(微线程)比操作系统线程更轻量级,通过`async def`定义并在遇到`await`表达式时暂停执行。异步函数利用`await`实现任务间的切换。事件循环作为异步编程的核心,负责调度任务;`asyncio`库提供了事件循环的管理。Future对象则优雅地处理异步结果。掌握这些概念,可使代码更高效、简洁且易于维护。
26 1
|
2月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
60 3
|
2月前
|
Java Serverless Python
探索Python中的并发编程与`concurrent.futures`模块
探索Python中的并发编程与`concurrent.futures`模块
28 4
|
2月前
|
UED 开发者 Python
Python并发编程新纪元:异步编程如何重塑IO与CPU密集型任务的处理方式?
在Python编程中,异步编程作为一种非阻塞模式,通过允许程序在等待IO操作时继续执行其他任务,提高了程序的响应性和吞吐量。与传统同步编程相比,它减少了线程等待时间,尤其在处理IO密集型任务时表现出色,如使用`asyncio`库进行异步HTTP请求。尽管对CPU密集型任务的直接提升有限,但结合多进程或多线程可间接提高效率。异步编程虽强大,但也带来了代码复杂度增加和调试难度提升等挑战,需要开发者掌握最佳实践来克服这些问题。随着其技术的成熟,异步编程正在逐步改变我们处理IO与CPU密集型任务的方式,成为提升性能和优化用户体验的重要工具。
23 0
下一篇
无影云桌面