跳转至

并发

Python实现并发有3种方式:多进程、多线程、多进程+多线程

重点多线程和多进程的比较

以下情况需要使用多线程:

  1. 程序需要维护许多共享的状态(尤其是可变状态),Python中的列表、字典、集合都是线程安全的,所以使用线程而不是进程维护共享状态的代价相对较小。
  2. 程序会花费大量时间在I/O操作上,没有太多并行计算的需求且不需占用太多的内存。

以下情况需要使用多进程:

  1. 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。
  2. 程序的输入可以并行的分成块,并且可以将运算结果合并。
  3. 程序在内存使用方面没有任何限制且不强依赖于I/O操作(如:读写文件、套接字等)。

重点异步I/O与多进程的比较

当程序不需要真正的并发性或并行性,而是更多的依赖于异步处理和回调时,asyncio就是一种很好的选择。如果程序中有大量的等待与休眠时,也应该考虑asyncio,它很适合编写没有实时数据处理需求的Web应用服务器。


多进程

创建进程的时候,子进程复制父进程及其所有的数据结构 每个子进程都有独自的外部变量 不会共享修改后的结果等

from multiprocessing import Process

# 普通的函数
def 函数名(参数1, 参数2):
    # 代码

p1 = Process(target=函数名, args=(参数1, 参数2))
p1.start()  # 开始
p2 = Process(target=函数名, args=(参数1, 参数2))
p2.start()  # 开始
p1.join()  # 等等结束 结束后向下执行
p2.join()  # 等等结束 结束后向下执行

多线程

线程可以共享进程的内存空间 多个线程可以共享全局变量 多个线程共享同一个变量(我们通常称之为“资源”)如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源” 对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。

Python的多线程并不能发挥CPU的多核特性

线程函数

from threading import Thread

# 普通的函数
def 函数名(参数1, 参数2):
    # 代码

t1 = Thread(target=函数名, args=(参数1, 参数2))
t1.start()  # 开始
t2 = Thread(target=函数名, args=(参数1, 参数2))
t2.start()  # 开始
t1.join()  # 等等结束 结束后向下执行
t2.join()  # 等等结束 结束后向下执行

线程类

from threading import Thread


class 类名(Thread):
    def __init__(self, 参数1, 参数2):
        super().__init__()

    # 开始时执行
    def run(self):
        # 代码


t1 = 类名(参数1, 参数2)
t1.start()  # 开始
t2 = 类名(参数1, 参数2)
t2.start()  # 开始
t1.join()  # 等等结束 结束后向下执行
t2.join()  # 等等结束 结束后向下执行

在资源类上加锁

“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源” 而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁” 其他线程才有机会获得“锁”

代码
from threading import Thread, Lock
from time import sleep

class Account(object):

    def __init__(self):
        self._balance = 0
        self._lock = Lock()

    def deposit(self, money):
        # 先获取锁才能执行后续的代码
        self._lock.acquire()
        try:
            new_balance = self._balance + money
            sleep(0.01)
            self._balance = new_balance
        finally:
            # 在finally中执行释放锁的操作保证正常异常锁都能释放
            self._lock.release()

    @property
    def balance(self):
        return self._balance


class AddMoneyThread(Thread):

    def __init__(self, account, money):
        super().__init__()
        self._account = account
        self._money = money

    def run(self):
        self._account.deposit(self._money)


def main():
    account = Account()
    threads = []
    for _ in range(100):
        t = AddMoneyThread(account, 1)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print('账户余额为: ¥%d元' % account.balance)


if __name__ == '__main__':
    main()

多线程+队列

import queue
import threading

# 创建一个队列对象,把数组值放进去
q = queue.Queue(maxsize=4000)
for i in range(100):
    q.put(i)

# 定义实际操作
def do_something(i):
    print(i)

# 从队列中取出值,并调用实际操作
def f(queue):
    while not queue.empty():
        i = queue.get()
        do_something(i)

# 起10个线程,线程target去执行从队列中取值并进行操作的动作
threads = []
for t in range(10):
    thread = threading.Thread(target=f, args=(q,))
    threads.append(thread)
    thread.start()

for t in threads:
    t.join()

协程

在Python语言中,单线程+异步I/O的编程模型称为协程

import asyncio

async def say_after():
    await asyncio.sleep(2)  # 等待执行完成后向下执行
    # 代码


async def main():
    task1 = asyncio.create_task(say_after())  # 执行 并不等待 向下执行
    task2 = asyncio.create_task(say_after())  # 执行 并不等待 向下执行

    await task1  # 等待执行完成后向下执行
    await task2  # 等待执行完成后向下执行


asyncio.run(main())  # 执行