python 并发

 

进程

单进程

import time

def cook():
    for i in range(3):
        print("做饭...")
        time.sleep(0.5)

def wash():
    for i in range(3):
        print("洗衣服...")
        time.sleep(0.5)

if __name__ == '__main__':
    cook()
    wash()

运行结果

做饭...
做饭...
做饭...
洗衣服...
洗衣服...
洗衣服...

可使用多进程方式让两个任务同步执行

多进程

from time import sleep
from multiprocessing import Process

def cook():
    for i in range(3):
        print("做饭...")
        sleep(0.5)

def wash():
    for i in range(3):
        print("洗衣服...")
        sleep(0.5)

if __name__ == '__main__':

    # 使用进程类创建进程对象
    cook_process = Process(target=cook)
    wash_process = Process(target=wash)

    # 使用进程对象启动进程执行指定任务
    cook_process.start()
    wash_process.start()

运行结果

做饭...
洗衣服...
做饭...
洗衣服...
做饭...
洗衣服...

带参数

  • args 以元组形式给进程传递参数

  • kwargs 以字典形式给进程传递参数

from time import sleep
from multiprocessing import Process

def cook(num):
    for i in range(num):
        print("做饭...")
        sleep(0.5)

def wash(num):
    for i in range(num):
        print("洗衣服...")
        sleep(0.5)

if __name__ == '__main__':
    # 使用进程类创建进程对象
    cook_process = Process(target=cook, args=(2, ))
    wash_process = Process(target=wash, args=(5, ))

    # cook_process = Process(target=cook, kwargs={"num": 2})
    # wash_process = Process(target=wash, kwargs={"num": 2})

    # 使用进程对象启动进程执行指定任务
    cook_process.start()
    wash_process.start()

运行结果

做饭...
洗衣服...
做饭...
洗衣服...
洗衣服...
洗衣服...
洗衣服...

进程信息

  • 获取当前进程编号
import os

pid = os.getpid()
print(pid)
  • 获取当前进程父进程编号
import os
from time import sleep
from multiprocessing import Process

def cook():
    print("做饭进程""pid: ", os.getpid())
    sleep(0.5)
    print("做饭进程父进程pid: ", os.getppid())

def wash():
    print("洗衣服进程pid: ", os.getpid())
    sleep(0.5)
    print("洗衣服进程父进程pid: ", os.getppid())
        

if __name__ == '__main__':
    print("主进程pid: ", os.getpid())

    cook_process = Process(target=cook)
    wash_process = Process(target=wash)

    cook_process.start()
    wash_process.start()

运行结果

主进程pid:  17144
做饭进程pid:  16528
洗衣服进程pid:  16520
做饭进程父进程pid:  17144
洗衣服进程父进程pid:  17144

守护进程

主进程会等待所有子进程执行结束再结束, 实际开发过程中则希望主进程结束时结束所有子进程, 此时主进程需创建守护进程

守护进程特点,

  • 守护进程会在主进程代码执行结束后就终止

  • 守护进程内无法再开启子进程, 否则抛出异常

AssertionError: daemonic processes are not allowed to have children
  • 进程之间是互相独立的, 主进程代码运行结束, 守护进程随即终止
import os
from time import sleep
from multiprocessing import Process

def work():
    for i in range(10):
        print("Work in [{0}]".format(i))
        sleep(0.5)

if __name__ == '__main__':
    # 创建子进程
    work_process = Process(target=work)

    # 设置守护进程, 主进程结束后子进程直接销毁
    work_process.daemon = True

    work_process.start()

    sleep(2)

    print("主进程结束!")

运行结果

Work in [0]
Work in [1]
Work in [2]
Work in [3]
主进程结束!

主进程结束时子进程也立即结束

线程

定义

进程是由若干线程组成, 一个进程至少有一个线程

线程是操作系统直接支持的执行单元

python线程是真正Posix Thread, 而不是模拟线程

from time import sleep
from threading import Thread, current_thread

def loop():
    name = current_thread().name
    for i in range(5):
        print('子线程 [{0}_{1}]运行'.format(name, i))
        sleep(0.5)

if __name__ == "__main__":
    name = current_thread().name
    print('线程 [{0}] 运行...'.format(name))

    # 子线程命名LoopThread, 否则Python自动命名为Thread-1, Thread-2..., 名字无意义, 仅仅在打印时显示
    t = Thread(target=loop, name='LoopThread')

    t.start()
    t.join()

    print("线程 [{0}] 结束".format(name))

任何进程默认就会启动一个主线程, 主线程又可以启动新线程

threading模块current_thread()函数, 永远返回当前线程实例

主线程实例名叫MainThread

互斥锁

多进程中, 同一个变量各有一份拷贝存在于每个进程中, 互不影响

多线程中, 所有变量都由所有线程共享, 任何变量都可以被任何一个线程修改

因此线程之间共享数据最大危险在于多个线程同时改一个变量, 造成内容混乱

  • 使用
import threading

lock = threading.Lock()

# 获取锁
lock.acquire()

# 释放锁
lock.release()

上下文管理

import threading

lock = threading.Lock()

with lock:
    pass

with 语句会在执行前自动获取锁, 执行结束后自动释放

例如银行存款时, 多线程操作时, 每一次存钱与取钱前都需要加锁, 避免余额错误

from threading import Thread, Lock

g_balance = 0

lock = Lock()

def change_it(n):
    # 先存后取, 结果应该为0
    global g_balance

    g_balance += n
    g_balance -= n

def run_thread(n):
    global lock

    for i in range(1000):
        with lock:
            change_it(i)

if __name__ == '__main__':
    lock = Lock()

    t1 = Thread(target=run_thread, args=(5, ))
    t2 = Thread(target=run_thread, args=(8, ))

    threads = [
        Thread(target=run_thread, args=(5, )), 
        Thread(target=run_thread, args=(8, ))
    ]

    for i in threads:
        i.start()

    for i in threads:
        i.join()

    print(g_balance)