1、概念

以下仅为个人理解

  • 进程——在windows的任务管理器里看到的一个个程序就是进程,最常看见的多进程就是浏览器的多进程,一个程序启动后至少会有一个进程,通常为了效率,会给多核CPU每个核分配几个进程以充分利用CPU。进程之间互不影响,一个进程挂了并不会影响另一个进程。
  • 线程——一个进程至少会有一个线程,线程是最小的执行单位。一个进程可以有多线程,多线程可以提高效率。一个线程挂了会导致该进程挂了从而该进程下的线程都会挂掉。
  • 协程——协程类似于线程,但在控制上稍有不同,是比线程更轻便的一种运行方式,通常和异步IO相关。
  • 并行——并行就是指同时执行多个任务。
  • 并发——和并行差不多意思,应用场合不同。
  • 同步——一个任务执行完了再去执行下一个任务。
  • 异步——一个任务执行到一半转而执行另一个任务收到消息后再回头处理等待的任务

这里有一个更为详细和硬核的教程:https://github.com/denglj/aiotutorial

2、多进程

Python由于CPython解释器的原因,似乎对多进程并不支持,但是可以通过multiprocessing调用多个解释器来实现多进程。

from multiprocessing import Process
import os
import time


# 子进程要执行的代码
def run_proc(name):
    print('Run child process %d (%s)...' % (name, os.getpid()))
    time.sleep(5)


if __name__ == '__main__':
    start = time.perf_counter()
    print('Parent process %s.' % os.getpid())
    p = []
    for i in range(5):
        p.append(Process(target=run_proc, args=(i,)))
    print('Child process will start.')
    for i in range(5):
        p[i].start()
    [x.join() for x in p]
    print('Child process end.')
    end = time.perf_counter()
    print('cost time:', end - start)

从最后的运行时间可以看出该代码实现了5个进程的并行,从任务管理器里也可以看到多个Python进程。
也可以通过pool模块来开启多进程,其中运行中的进程数和CPU的核数是匹配的。

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print('All subprocesses done.')

3、多线程

实现

多线程要用到threading模块,新线程调用函数的方法一般有两种,一种是直接调用threading.Thread等指定target的参数,一种是通过新写一个对象继承threading.Thread,然后重写run方法。

import time
import threading

def water():
    print('开始烧水')
    time.sleep(5)
    print('水烧开了')


def rice():
    print('开始煮饭')
    time.sleep(10)
    print('饭煮熟了')


start = time.time()
t1=threading.Thread(target=water)
t2=threading.Thread(target=rice)
t1.start()
t2.start()
t1.join()
t2.join()
stop = time.time()
print(stop - start)

一个线程锁的结构通常如下,线程的运行通常是并行的,如果线程之间需要共同处理某个数据,那么该数据可能就会失控,这是我们就希望一个线程在处理数据时别的线程不要插手,这就是锁的作用。

lock = threading.Lock()
def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            change_it(n)
        finally:
            # 释放锁:
            lock.release()

join

在程序末尾经常能看到一个join函数,该函数本来的作用应该是在某处插入一个线程,并且只有当该线程结束时才能继续执行其他命令。被用在程序最后是为了保证所有子线程结束后再结束主线程,没有这句你就会发现你的主线程会比子线程先结束。

守护线程

一个程序的线程之间可以是不平等的,分为普通线程和守护线程。进程也有守护进程,守护进程会在主进程代码运行结束的情况下,立即挂掉。普通线程在主线程结束后仍然可以继续运行,引用说法“该进程内所有非守护线程全部都运行完毕后,守护线程才会挂掉”。这东西到底有什么用我还没搞清楚。。。
守护线程的实现方法是设置线程的.daemon=True

4、协程

Python的协程是通过yield来完成的,yield本质是一个生成器,可以通过next()来进行迭代,还可以通过send()来传递参数,多任务并发使用起来很方便。

import time

def func1():
    while True:
        yield
        time.sleep(1)
        print('func1')

def func2():
    g = func1()
    for i in range(3):
        next(g)
        time.sleep(1)
        print('func2')

start = time.time()
func2()
stop = time.time()
print(stop - start)

协程想要并行还需要借用其他一些库比如asyncio

import threading
import time
import asyncio


@asyncio.coroutine
def water():
    print('开始烧水')
    yield from asyncio.sleep(5)
    print('水烧开了')


@asyncio.coroutine
def rice():
    print('开始煮饭')
    yield from asyncio.sleep(10)
    print('饭煮熟了')


start = time.time()
loop = asyncio.get_event_loop()
tasks = [water(), rice()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
stop = time.time()
print(stop - start)

这个程序最终需要时间仅为10秒多点,因此是并行的,但要注意这利用的是 yield from asyncio.sleep(5),这里代表的是一个IO阻塞,并不是什么函数放在这里都能够并行的。

Last modification:January 2nd, 2020 at 12:34 am