python 多进程和多线程


一、进程和线程

1、概念

进程: 一个进程就是一个任务,可以理解为一个程序。一个进程可以有多个线程,至少一个。多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响。
线程: 多线程中,所有变量都由所有线程共享,任何一个变量都可以被任何一个线程修改。

2、优缺点

进程: 创建进程开销(时间和空间)较大,一个子进程崩溃了,不会影响主进程和其他子进程。

线程: 创建线程开销(时间和空间)相对较小,任何一个线程挂掉都可能直接造成整个进程崩溃。


二、多进程

1、使用多进程

from multiprocessing import Process
import os

def run_proc(name):
    """
    子进程函数
    """
    print('Run child process %s: %s...' % (name, os.getpid()))

if __name__ == '__main__':
    # 主进程 id
    print('Parent process: %s.' % os.getpid())
    # 创建子进程
    p = Process(target=run_proc, args=('test',))
    print('Child process will start.')
    # 开启子进程
    p.start()
    # 等待子进程运行结束
    p.join()
    print('Child process end.')

2、进程池

from multiprocessing import Pool
import os
import time
import random

def 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__':
    # 主进程 id
    print('Parent process %s.' % os.getpid())
    # 指定进程池中进程最大个数,默认为电脑的核数
    p = Pool(4)
    # 同时开启 5 个进程
    for i in range(5):
        p.apply_async(task, args=(i,))
    print('Waiting for all subprocesses done...')
    # 关闭线程池,不再继续添加新的Process
    p.close()
    # 等待所有子进程执行完毕
    p.join()
    print('All subprocesses done.')

3、进程间通信:Queue

from multiprocessing import Process, Queue
import os
import time
import random


def write(q):
    """
    添加数据到队列中
    """
    print('Process to write: %s' % os.getpid())
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())


def read(q):
    """
    从队列中读取数据
    """
    print('Process to read: %s' % os.getpid())
    while True:
        # 读取数据,队列为空时,True:会进入等待状态,直到超时,False:抛出 Empty 异常
        value = q.get(True)
        print('Get %s from queue.' % value)


if __name__ == '__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    # 创建两个进程
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程 pw 写入
    pw.start()
    # 启动子进程 pr 读取
    pr.start()
    # 等待 pw 结束:
    pw.join()
    # pr进程是死循环,无法等待其结束,只能强行终止:
    pr.terminate()

三、多线程

1、使用多线程

import threading
import time


def loop():
    """
    子线程任务函数
    """
    print('thread %s is running...' % threading.current_thread().name)
    time.sleep(2)
    print('thread %s ended.' % threading.current_thread().name)


if __name__ == '__main__':
    # 主线程名
    print('thread %s is running...' % threading.current_thread().name)
    # 创建子线程
    t = threading.Thread(target=loop, name='LoopThread')
    # 开启子线程
    t.start()
    # 等待子线程运行完毕
    t.join()
    print('thread %s ended.' % threading.current_thread().name)

2、线程锁

import threading

balance = 0
lock = threading.Lock()

def change_it(n):
    """
    先加后减,结果应该为 0
    """
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    """
    子线程任务函数
    """
    for i in range(100000):
        # 先要获取锁
        lock.acquire()
        try:
            # 只有获得锁的线程才能进行调用
            change_it(n)
        finally:
            # 用完一定要释放锁,这样别的线程才有机会使用
            lock.release()

if __name__ == '__main__':
    # 创建线程
    t1 = threading.Thread(target=run_thread, args=(5,))
    t2 = threading.Thread(target=run_thread, args=(8,))
    # 开启线程
    t1.start()
    t2.start()
    # 线程阻塞
    t1.join()
    t2.join()
    print(balance)

3、全局锁:GIL 锁

  • Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
  • Python 虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python 进程有各自独立的GIL锁,互不影响。

4、线程自己的全局变量:ThreadLocal

import threading

# 创建全局 ThreadLocal 对象
local_school = threading.local()

def process_student():
    """
    获取当前线程关联的全局变量 student
    """
    # 获得各自线程的全局变量
    std = local_school.student
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))

def process_thread(name):
    """
    子线程任务函数
    """
    # 绑定 ThreadLocal 的student:
    local_school.student = name
    process_student()

if __name__ == '__main__':
    t1 = threading.Thread(target=process_thread, args=('A',), name='Thread-A')
    t2 = threading.Thread(target=process_thread, args=('B',), name='Thread-B')
    t1.start()
    t2.start()
    t1.join()
    t2.join()

>>> Hello, A (in Thread-A)
	Hello, B (in Thread-B)

GOOD LUCK!