zzdshidashuaige

1.线程概述

操作系统可以同时运行多个任务,一个任务通常是一个程序,每一个运行中的程序就是一个进程。当一个程序运行时,内部可能包含多个顺序执行流,每一个顺序执行流就是一个线程。

并发(concurrency):同一时刻有多条指令在多个处理器上同时执行。

并行(parallel):同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

大部分操作系统都支持多进程并发执行,可以同时执行多个任务。对于一个CPU而言,在某个时间点它只能执行一个程序。也就是只能运行一个进程,CPU不断地在这些进程之间轮换执行。

多线程是同一个进程可以同时并发处理多个任务。线程是进程的执行单元。线程可以完成一定的任务,可以与其他进程共享父进程中的共享变量及部分环境,相互之间协同来完成进程要完成的任务。线程是独立运行的,他并不知道进程中是否还有其他线程存在,它的运行是抢占式的。一个程序运行后至少有一个进程,在一个进程中至少有一个主线程。

2.线程的创建与启动

调用Thread类的构造器创建线程很简单,直接调用threading.Thread类的如下构造器创建线程。

_init_(self,group=None,target=None,name=None,args=(),kwargs=None,*,daemon=None)

  • group:指定该线程所属的线程组。
  • target:指定该线程要调度的目标方法。
  • args:指定一个元组,以位置参数的形式为target指定的函数传入参数。元组的第一个元素传给target函数的第一个参数,元组的第二个元素传给target函数的第二个参数。。。
  • kwargs:指定一个字典以关键字参数的形式为target指定的函数传入参数。
  • daemon:指定所构建的线程是否为后台线程。
  1. 通过Thread类的构造器创建线程对象。在创建线程对象时,target参数指定的函数将作为线程执行体。
  2. 调用线程对象的start方法启动该线程。
import threading

# 定义一个普通的action函数,该函数准备作为线程执行体
def action(max):
    for i in range(max):
        # 调用threading模块current_thread()函数获取当前线程
        # 线程对象的getName()方法获取当前线程的名字
        print(threading.current_thread().getName() +  " " + str(i))
# 下面是主程序(也就是主线程的执行体)
for i in range(100):
    # 调用threading模块current_thread()函数获取当前线程
    print(threading.current_thread().getName() +  " " + str(i))
    if i == 20:
        # 创建并启动第一个线程
        t1 =threading.Thread(target=action,args=(100,))
        t1.start()
        # 创建并启动第二个线程
        t2 =threading.Thread(target=action,args=(100,))
        t2.start()
print(\'主线程执行完成!\')

上面程序中的主程序包含一个循环,当循环变量i等于20时创建并启动2个新线程。

在进行多线程编程时,不要忘记Python程序运行时默认的主线程,主程序部分(没有放在任何函数中的代码)就是主线程的线程执行体。

说穿了很简单,多线程就是让多个函数能并发执行,让普通用户感觉到多个函数同时执行。

3.继承Thread类创建线程类

通过继承Thread类来创建并启动线程的步骤如下:

  1. 定义Thread的子类并重写该类的run方法。run方法的方法体就代表了线程需要完成的任务,因此把run方法称为线程执行体。
  2. 创建Thread子类的实例,即创建线程对象。
  3. 调用线程对象的start方法启动线程。
import threading

# 通过继承threading.Thread类来创建线程类
class FkThread(threading.Thread):
    def __init__(self):  
        threading.Thread.__init__(self)
        self.i = 0
    # 重写run()方法作为线程执行体
    def run(self):  
        while self.i < 100:
            # 调用threading模块current_thread()函数获取当前线程
            # 线程对象的getName()方法获取当前线程的名字
            print(threading.current_thread().getName() +  " " + str(self.i))
            self.i += 1
# 下面是主程序(也就是主线程的执行体)
for i in range(100):
    # 调用threading模块current_thread()函数获取当前线程
    print(threading.current_thread().getName() +  " " + str(i))
    if i == 20:
        # 创建并启动第一个线程
        ft1 = FkThread()
        ft1.start()
        # 创建并启动第二个线程
        ft2 = FkThread()
        ft2.start()
print(\'主线程执行完成!\')

4.线程的生命周期

线程要经过新建、就绪、运行、阻塞、死亡,也会多次在运行和就绪之间切换。

4.1新建和就绪状态

当程序创建了一个Thread对象或Thread子类的对象之后该线程就处于新建状态;当线程对象调用start方法之后该线程就处于就绪状态,并没有开始运行。

import threading

# 定义准备作为线程执行体的action函数
def action(max):
    for i in range(max):
        # 直接调用run()方法时,Thread的name属性返回的是该对象的名字
        # 而不是当前线程的名字
        # 使用threading.current_thread().name总是获取当前线程的名字
        print(threading.current_thread().name +  " " + str(i))  # ①
for i in range(100):
    # 调用Thread的currentThread()方法获取当前线程
    print(threading.current_thread().name +  " " + str(i))
    if i == 20:
        # 直接调用线程对象的run()方法
        # 系统会把线程对象当成普通对象,把run()方法当成普通方法
        # 所以下面两行代码并不会启动两个线程,而是依次执行两个run()方法
        threading.Thread(target=action,args=(100,)).run()
        threading.Thread(target=action,args=(100,)).run()
4.2运行和阻塞状态

如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。如果计算机只有一个CPU,那么任何时候只有一个线程处于运行状态。在一个具有多处理器的机器上,将会有多个线程并行执行;当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的情况。

当发生如下情况,线程将会进入阻塞状态。

  • 线程调用sleep方法主动放弃其所占用的处理器资源。
  • 线程调用了一个阻塞式I/O方法,在该方法返回之前,该线程被阻塞。
  • 线程试图获得一个锁对象,但该锁对象正被其他线程所持有。
  • 线程在等待某个通知。

当发生如下情况,线程将会解除阻塞状态,重新进入就绪状态,等待调度器再次调用它。

  • 调用sleep方法的线程经过了指定的时间。
  • 线程调用的阻塞式I/O方法已经返回。
  • 线程成功获得了试图获取的锁对象。
  • 线程正在等待某个通知时,其他线程发出了一个通知。
4.3线程死亡

会以以下三种方式结束,结束后就处于死亡状态。

  • run方法或代表线程执行体的target函数执行完成,线程正常结束。
  • 线程抛出一个未捕获的异常。

为了测试线程是否已经死亡,可以调用线程对象的is_alive方法,当线程处于就绪、运行、阻塞三种状态返回True,线程处于新建死亡两种状态时返回False。

下面尝试对处于死亡状态的线程再次调用start方法,会报错:

import threading

# 定义action函数准备作为线程执行体使用
def action(max):
    for  i in range(100):
        print(threading.current_thread().name +  " " + str(i))
# 创建线程对象
sd = threading.Thread(target=action, args=(100,))
for i in range(300):
    # 调用threading.current_thread()函数获取当前线程
    print(threading.current_thread().name +  " " + str(i))
    if i == 20:
        # 启动线程
        sd.start()
        # 判断启动后线程的is_alive()值,输出True
        print(sd.is_alive())
    # 当线程处于新建、死亡两种状态时,is_alive()方法返回False
    # 当i > 20时,该线程肯定已经启动过了,如果sd.is_alive()为False时
    # 那就是死亡状态了
    if i > 20 and not(sd.is_alive()):
        # 试图再次启动该线程
        sd.start()

5控制线程

5.1join线程

Thread提供了让一个线程等待另一个线程完成的方法,join方法。当在某个程序执行流中调用其他线程的join方法时,调用线程将被阻塞,直到join方法的线程完成。

join方法通常由使用线程的程序调用,以将大问题划分成许多小问题,并将每个小问题分配一个线程。当所有小问题处理完再调用主线程进一步操作。

import threading

# 定义action函数准备作为线程执行体使用
def action(max):
    for i in range(max):
        print(threading.current_thread().name + " " + str(i))
  
# 启动子线程
threading.Thread(target=action, args=(100,), name="新线程").start()
for i in range(100):
    if i == 20:
        jt = threading.Thread(target=action, args=(100,), name="被Join的线程")
        jt.start()
        # 主线程调用了jt线程的join()方法
        # 主线程必须等jt执行结束才会向下执行
        jt.join()
    print(threading.current_thread().name + " " + str(i))

上面一共有3个线程,主程序开始就启动了名为新线程的子线程,会和主线程并发执行。当主线程的循环变量等于20,启动了名为被join的线程的线程,该线程不会和主线程并发执行,会和新线程这个子线程并发执行。直到被join的线程执行完主线程才会执行。

join(timeout=None)方法可以指定一个timeout参数,该参数指定被join的线程的时间最长为多少秒。

5.2后台进程

有一种线程他是在后台运行的,它的任务是为其他进程提供服务,这种线程被称为后台线程。Python解释器的垃圾回收线程就是后台线程。

如果所有的前台线程都死亡了,那么后台线程会自动死亡。

调用Thread对象的daemon属性可以将指定线程设置为后台线程。

import threading

# 定义后台线程的线程执行体与普通线程没有任何区别
def action(max):
    for i in range(max):
        print(threading.current_thread().name + "  " + str(i))
t = threading.Thread(target=action, args=(100,), name=\'后台线程\')
# 将此线程设置成后台线程
# 也可在创建Thread对象时通过daemon参数将其设为后台线程
t.daemon = True
# 启动后台线程
t.start()
for i in range(10):
    print(threading.current_thread().name + "  " + str(i))
# -----程序执行到此处,前台线程(主线程)结束------
# 后台线程也应该随之结束

t线程应该执行到99时才会结束,但在主线程结束后,后台线程也结束了。

5.3线程睡眠sleep

如果要让在执行的线程暂停一段时间并进入阻塞状态可以用time模块的sleep函数。

import time

for i in range(10):
    print("当前时间: %s" % time.ctime())
    # 调用sleep()函数让当前线程暂停1s
    time.sleep(1)

6.线程同步

6.1线程安全问题

银行取钱问题:

  1. 用户输入账户、密码,系统判断用户的账户密码是否匹配
  2. 用户输入取款金额
  3. 系统判断账户余额是否大于取款金额
  4. 如果余额大于取款金额,则取款成功,否则失败

按照上述流程编写程序,并使用两个线程模拟两个人使用同一账户并发取钱操作。此处忽略检查账户和密码的操作。先定义一个账户类,该账户类封装了账户编号和余额两个成员变量。

class Account:
    # 定义构造器
    def __init__(self,account_no,balance):
        # 封装账户编号和余额两个成员变量
        self.account_no = account_no
        self.balance = balance

接下来程序会定义一个模拟取钱的函数,该函数根据执行账户、取钱数量进行取钱操作,取钱的逻辑是当余额不足时无法提取现金,足时吐出钞票,余额减少。

import threading
import time
import Account

# 定义一个函数来模拟取钱操作
def draw(account, draw_amount):
    # 账户余额大于取钱数目
    if account.balance >= draw_amount:
        # 吐出钞票
        print(threading.current_thread().name\
            + "取钱成功!吐出钞票:" + str(draw_amount))
#        time.sleep(0.001)
        # 修改余额
        account.balance -= draw_amount
        print("\t余额为: " + str(account.balance))
    else:
        print(threading.current_thread().name\
            + "取钱失败!余额不足!")
# 创建一个账户
acct = Account.Account("1234567" , 1000)
# 模拟两个线程对同一个账户取钱
threading.Thread(name=\'甲\', target=draw , args=(acct , 800)).start()
threading.Thread(name=\'乙\', target=draw , args=(acct , 800)).start()

甲取钱成功!吐出钞票:800
乙取钱成功!吐出钞票:800
	余额为: 200	余额为: -600

有可能发生第12行的偶然错误结果,因为线程调度的不确定性。

6.2同步锁

之所以出现上面的错误结果是因为run方法的方法体不具有线程安全性——程序中有两个并发线程在修改Account对象;而且系统恰好在time.sleep处执行线程切换,切换到另一个修改Account对象的线程,所以就出现问题。

为解决这个问题,threading模块引入了锁(lock)。模块提供了lock和rlock两个类,他们都提供了如下两个方法来加锁和释放锁。

acquire(blocking=True,timeout=-1):请求对Lock或Rlock加锁,timeout指定加锁多少秒。

release():释放锁

Lock和RLock的区别如下:

threading.Lock:它是一个基本的锁对象,每次只能锁定一次,其余的锁请求需等待锁释放后才能获取。

threading.RLock:它代表可重入锁(Reentrant Lock)。对于可重入锁,在同一个线程中可以对它进行多次锁定,也可以多次释放。如果使用RLock,那么acquare()和release()方法必须成对出现。如果调用了n次acquire加锁,则必须调用n次release才能释放。

所以,RLock锁具有可重入性,即同一个线程可以对已被加锁的RLock锁再次加锁,RLock对象会维持一个计数器来追踪acquire方法的嵌套调用,线程在每次调用acquire加锁后,都必须显示调用release释放锁。所以,一段被锁保护的方法可以调用另一个被相同锁保护的方法。

Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程在开始访问共享资源之前应先请求获得Lock对象。当对共享资源访问完成后,程序释放对Lock对象的锁定。

在实现线程安全的控制中,常用的是RLock,格式如下:

class X:
    # 定义需要保证线程安全的方法
    def m():
        # 加锁
        self.lock.acquire()
        try:
            # 需要保证线程安全的代码
            # 。。。方法体
        # 使用finally块保证释放锁
        finally:
            self.lock.release()

使用RLock对象来控制线程安全,当加锁和释放锁出现在不同的作用范围内时,通常建议使用finally块来确保在必要时释放锁。

import threading
import time

class Account:
    # 定义构造器
    def __init__(self, account_no, balance):
        # 封装账户编号、账户余额的两个成员变量
        self.account_no = account_no
        self._balance = balance
        self.lock = threading.RLock()

    # 因为账户余额不允许随便修改,所以只为self._balance提供getter方法
    def getBalance(self):
        return self._balance
    # 提供一个线程安全的draw()方法来完成取钱操作
    def draw(self, draw_amount):
        # 加锁
        self.lock.acquire()
        try:
            # 账户余额大于取钱数目
            if self._balance >= draw_amount:
                # 吐出钞票
                print(threading.current_thread().name\
                    + "取钱成功!吐出钞票:" + str(draw_amount))
                time.sleep(0.001)
                # 修改余额
                self._balance -= draw_amount
                print("\t余额为: " + str(self._balance))
            else:
                print(threading.current_thread().name\
                    + "取钱失败!余额不足!")
        finally:
            # 修改完成,释放锁
            self.lock.release()

第十行定义了一个RLock对象,在程序实现draw方法时,进入该方法开始执行后立即请求对RLock对象加锁,当执行完draw方法的取钱逻辑之后使用finally块确保释放锁。

并发线程在任意时刻只有一个线程可以进入修改共享资源的代码区(也被称为临界区),所以在同一时刻最多只有一个线程处于临界区内,从而保证线程安全。

下面程序创建并启动了两个取钱线程:

import threading
import Account

# 定义一个函数来模拟取钱操作
def draw(account, draw_amount):
    # 直接调用account对象的draw()方法来执行取钱操作
    account.draw(draw_amount)
# 创建一个账户
acct = Account.Account("1234567" , 1000)
# 模拟两个线程对同一个账户取钱
threading.Thread(name=\'甲\', target=draw , args=(acct , 800)).start()
threading.Thread(name=\'乙\', target=draw , args=(acct , 800)).start()
甲取钱成功!吐出钞票:800
	余额为: 200
乙取钱失败!余额不足!
6.3死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序不会发生异常也不会给出提示,只是所有线程都处于阻塞状态无法继续。

7.线程通信

7.1使用condition实现线程通信
7.2使用队列(Queue)控制线程通信

queue模块提供了几个阻塞队列,主要用于线程通信。

queue.Queue(maxsize=0):代表先进先出的常规队列。maxsize可以限制队列的大小。如果队列的大小达到队列的上限就会加锁,再次加入元素就会被阻塞。将maxsize设置为0或复数,队列大小就是无限制的。

queue.LifoQueue(maxsize=0):后进先出。

PriorityQueue(maxsize=0):代表优先级队列,优先级最小的先出队列。

以上三个类都提供以下属性和方法:

Queue.qsize():返回队列的实际大小。

Queue.empty():判断队列是否为空。

Queue.full():是否已满。

Queue.put(item,block=True,timeout=None):向队列中放入元素。如果队列已满,且block参数为True(阻塞),当前线程被阻塞,timeout指定阻塞时间,如果将timeout设置为None,则代表一直阻塞,直到该队列的元素被消费;如果队列已满且block参数为False,则直接引发异常。

Queue.put_nowait(item):向队列中放入元素,不阻塞。

Queue.get(item,block=True,timeout=None):从队列中取出元素。如果队列已满且block参数为True,当前线程被阻塞,timeout指定阻塞时间,如果将timeout设置为None,则代表一直阻塞,直到该队列的元素被消费;如果队列已空且block参数为False,则直接引发异常。

Queue.get_nowait(item):从队列中取出元素,不阻塞。

import queue

# 定义一个长度为2的阻塞队列
bq = queue.Queue(2)
bq.put("Python")
bq.put("Python")
print("1111111111")
bq.put("Python")  # ① 阻塞线程
print("2222222222")

使用put方法尝试放入第三个元素将会阻塞线程;与此类似,在queue已空的情况下,使用get方法尝试取出元素将会阻塞线程。

使用Queue实现线程通信:

import threading
import time
import queue

def product(bq):
    str_tuple = ("Python", "Kotlin", "Swift")
    for i in range(99999):
        print(threading.current_thread().name + "生产者准备生产元组元素!")
        time.sleep(0.2);
        # 尝试放入元素,如果队列已满,则线程被阻塞
        bq.put(str_tuple[i % 3])
        print(threading.current_thread().name \
            + "生产者生产元组元素完成!")
def consume(bq):
    while True:
        print(threading.current_thread().name + "消费者准备消费元组元素!")
        time.sleep(0.2)
        # 尝试取出元素,如果队列已空,则线程被阻塞
        t = bq.get()
        print(threading.current_thread().name \
            + "消费者消费[ %s ]元素完成!" % t)
# 创建一个容量为1的Queue
bq = queue.Queue(maxsize=1)
# 启动3个生产者线程
threading.Thread(target=product, args=(bq, )).start()
threading.Thread(target=product, args=(bq, )).start()
threading.Thread(target=product, args=(bq, )).start()
# 启动一个消费者线程
threading.Thread(target=consume, args=(bq, )).start()

上面程序启动了三个生产者线程向Queue队列中放入元素,启动了三个消费者线程从Queue队列中取出元素。本程序中队列的大小为1,因此生产者线程无法连续放入元素,必须等待消费者线程取出一个元素后,其中的一个生产者线程才能放入一个元素。

7.3使用Event控制线程通信

8.线程池

线程池在系统启动时即创建大量空闲线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态等待执行下一个函数。

线程池的基类是concurrent.futures模块中的Executor,它提供了两个子类,即ThreadPoolExecutor和ProcessPoolExecutor。前者用于创建线程池,后者创建进程池。

如果使用线程池/进程池来管理并发编程,只要将对应的task函数提交给线程池/进程池,剩下的事情就由他们来搞定。

Exectuor提供了如下方法:

  • submit(fn,*args,**kwargs):将fn函数提交给线程池。*args代表传给fn函数的参数,*kwargs代表以关键字参数的形式为fn函数传入参数。
  • map(func,*iterables,timeout=None,chunksize=1):该函数类似于全局函数map,只是该函数会启动多个线程,以异步方式立即对iterables执行map处理。
  • shutdown(wait=True):关闭线程池。

程序将task函数提交(submit)给线程池后,submit方法会返回一个Future对象,Future类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此线程执行的函数相当于一个将来完成的任务,所以使用Future来代表。

Future提供了如下方法:

  • cancel():取消该Future代表的线程任务。如果该任务正在执行,不可取消,则该方法返回False;否则程序会取消该任务并返回True。
  • cancelled():返回Future代表的线程任务是否被取消成功。
  • running():如果该Future代表的线程任务正在执行、不可被取消,该方法返回True。
  • done():如果该Future代表的线程任务被成功取消或执行完成,返回True。
  • result(timeout=None):获取该Future代表的线程任务最后返回的结果。如果Future代表的线程任务还未完成,该方法会阻塞当前线程。
  • exception(timeout=None):获取该Future代表的线程任务所引发的异常。如果任务成功完成返回None。
  • add_done_callback(fn):为该Future代表的线程任务注册一个回调函数,当该任务成功完成时会自动触发该fn函数。

在用完一个线程池后应该调用该线程池的shutdown方法,该方法将启动线程池的关闭序列。调用shutdown方法后的线程池不再接收新任务,但会将以前所有的已提交任务执行完成后,该线程池中的所有线程都会死亡。

使用线程池执行线程任务步骤如下:

  1. 调用ThreadPoolExecutor类的构造器创建一个线程池。
  2. 定义一个普通函数作为线程任务。
  3. 调用ThreadPoolExecutor对象的submit方法来提交线程任务。
  4. 当不想提交任何任务时,调用ThreadPoolExecutor对象的shutdown方法关闭线程池。
from concurrent.futures import ThreadPoolExecutor
import threading
import time

# 定义一个准备作为线程任务的函数
def action(max):
    my_sum = 0
    for i in range(max):
        print(threading.current_thread().name + \'  \' + str(i))
        my_sum += i
    return my_sum
# 创建一个包含2条线程的线程池
pool = ThreadPoolExecutor(max_workers=2)
# 向线程池提交一个task, 50会作为action()函数的参数
future1 = pool.submit(action, 50)
# 向线程池再提交一个task, 100会作为action()函数的参数
future2 = pool.submit(action, 100)
# 判断future1代表的任务是否结束
print(future1.done())
time.sleep(3)
# 判断future2代表的任务是否结束
print(future2.done())
# 查看future1代表的任务返回的结果
print(future1.result())
# 查看future2代表的任务返回的结果
print(future2.result())
# 关闭线程池
pool.shutdown()

第13行创建了一个包含两个线程的线程池,接下来只要将action函数提交(submit)给线程池,线程池就会负责启动线程来执行action函数。

当程序把action函数提交给线程池时,submit方法会返回该任务所对应的Future对象,程序会立即判断future1的done方法,该方法返回False,表面该任务还未完成。接下来主程序暂停3秒,然后判断future2的done方法,如果此时该任务已经完成,那么该方法将会返回True。

9.多进程

9.1使用multiprocessing.Process创建新进程

Python在multiprocessing模块下提供了Process来创建新进程。有两种方式创建新进程:

  • 以指定函数作为target,创建Process对象即可创建。
  • 继承Process类,并重写它的run方法来创建进程类,程序创建Process子类的实例作为进程。

Process类也有如下类似方法和属性:

  • run():重写该方法可实现进程的执行体。
  • start():该方法用于启动进程。
  • join([timeout]):当前进程必须等待被join的进程执行完成才能向下执行。
  • name:用于设置或访问进程的名字。
  • is_alive():判断进程是否还活着。
  • daemon:该属性用于判断或设置进程的后台状态。
  • pid:返回进程的ID。
  • authkey:返回进程的授权key。
  • terminate():中断进程。

下面是以指定函数作为target来创建新进程:

import multiprocessing
import os

# 定义一个普通的action函数,该函数准备作为进程执行体
def action(max):
    for i in range(max):
        print("(%s)子进程(父进程:(%s)):%d" % 
            (os.getpid(), os.getppid(), i))
if __name__ == \'__main__\':
    # 下面是主程序(也就是主进程)
    for i in range(100):
        print("(%s)主进程: %d" % (os.getpid(), i))
        if i == 20:
            # 创建并启动第一个进程
            mp1 = multiprocessing.Process(target=action,args=(100,))
            mp1.start()
            # 创建并启动第一个进程
            mp2 = multiprocessing.Process(target=action,args=(100,))
            mp2.start()
            mp2.join()
    print(\'主进程执行完成!\')

通过multiprocessing.Process创建并启动进程时,程序要先判断if _name_ == \'__main__\':否则会报错。由于调用了mp2.join(),因此主程序必须等mp2进程完成后才能向下执行。

9.2进程通信

python为进程通信提供了两种机制

  • Queue:一个进程向Queue中放入数据,另一个进程从Queue中读取数据。
  • Pipe:代表两个进程的管道
import multiprocessing

def f(q):
    print(\'(%s) 进程开始放入数据...\' % multiprocessing.current_process().pid)
    q.put(\'Python\')
if __name__ == \'__main__\':
    # 创建进程通信的Queue
    q = multiprocessing.Queue()
    # 创建子进程
    p = multiprocessing.Process(target=f, args=(q,))
    # 启动子进程
    p.start()
    print(\'(%s) 进程开始取出数据...\' % multiprocessing.current_process().pid)
    # 取出数据
    print(q.get())  # Python
    p.join()

分类:

技术点:

相关文章: