Python(七)并发编程-实践1 (进程,线程,协程)

一、进程的使用 (在应用程序当中,掌握一种开启子进程的方式,把程序当中的任务放到子进程里面去运行,实现并发的效果)

    1.1、进程(子进程)的创建与销毁    基于主进程开启的子进程,PID一定是不同的。主进程开启后,会等到子进运行完毕,主进程才会关闭。

        1)、进程的创建:进程创建在操作系统里。
        2)、进程的销毁:操作系统不做这件事了,把进程所占用的资源释放掉。

    1.2、开启子进程的2种方式  用到-multiprocessing 模块   

   方式一:调用类 Process 并实例化对象执行 --常用方法 (子进程不能无限开,每个子进程都会占用系统资源,当请求量过大时,开启过多的子进程会导致内存跑满。需要对开启子进程的数量做一个限制。需要用到进程池 -见3.11 )

import time, os
from multiprocessing import Process

def task(name):
    print("%s is runing, 子进程PID:%s" %(name,os.getpid()))
    time.sleep(3)
    print("%s is done, 子进程PID:%s" %(name,os.getpid()))

if __name__ == '__main__':
    # windows下开子进程必须在"main"下,因为在创建子进程时,会调用createprocess模块(会把父进程的内容导一遍,把父进程里面的数据完完整整的生成一份给子进程)
    # 在linux系统下,不会有这个问题(两种系统造子进程的方式不一样)

    p=Process(target=task,args=('子进程1',))
    # 传参方式一:
    # 因为导入的Process是一个类,所以需要实例化
    # target:起的进程需要执行哪一段代码(这里指task)
    # args:传参的方式,这里是一个元组(括号内需要加括号)

    # p=Process(target=task,kwargs={'x':'任务1'})
    # 传参方式二:
    # kwargs:按照字典的方式进行传参,key是函数的参数'x',value是参数对应的值。

    p.start()  # 这是就是给操作系统发送一个开启子进程的信号,告诉操作系统有一个子进程要起来,让操作系统去申请内存空间,然后把父进程里面的数据拷贝一个给子进程,然后调用CPU来运行。(这里并未把子进程"创造"出来,应为应用程序无法调用机器硬件去创建子进程,只能调用操作系统去创建子进程)
    # 子进程被造出来后, 和父进程就没有任何关系,因为进程之间是隔离的

    print('主进程, 子进程PID:%s' %os.getpid())
  # 在操作系统创建子进程的这个时间,这一行代码就已经被运行了 (所以下面显示的是这一行代码先打印出来)

# 通过进程的PID号证明了,运行这个代码同时有2个进程在运行,对应2个PID。
>>:主进程, 子进程PID:41708
>>:子进程1 is runing, 子进程PID:41709
>>:子进程1 is done, 子进程PID:41709

    方式二:自定义类 MyProcess 然后继承 Process 类的方式,MyProcess类里面必须要创建一个run()函数 [ 这个函数内容同"方法一"里面的task(name)函数内容一样,即子进程开启后运行的函数,也就是'方法一'里面Process(target="函数名") ]

from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name

    def run(self):     # 这里是固定写法,run函数内容同"方法一"里面的task(name)内容相同
        print("%s is runing, 子进程PID:%s" %(self.name,os.getpid()))
        time.sleep(3)
        print("%s is done, 子进程PID:%s" %(self.name,os.getpid()))

p=MyProcess("sudada")  # 这里本来要传一个 target='函数',这里的run()函数就等于这个'函数'也就是target=run,这里定义固定的run()函数即可。
p.start()              # 执行p.start()就是去调用MyProcess下面的run()函数代码
print('主进程, 子进程PID:%s' %os.getpid())

>>:主进程, 子进程PID:85091
>>:sudada is runing, 子进程PID:85102
>>:sudada is done, 子进程PID:85102

 

    1.3、进程的PID与PPID   (相同的程序运行多次,等于开启了多个进程(每个进程的PID不同))

    1、PID  (进程的ID),PPID  (进程的父进程)

from multiprocessing import Process
import time
import os

def task():
    print('子进程的PID:%s,子进程的PPID:%s' %(os.getpid(),os.getppid()))

if __name__ == '__main__':
    p=Process(target=task,)
    p.start()
    print('主进程PID:',os.getpid())

>>:主进程PID: 16660
>>:子进程的PID:16916,子进程的PPID:16660

    可以看出,子进程的PPID就等于父进程的PID!

 

    1.4、子进程对象的join方法   (主进程在等待子进程运行完毕之后,在运行下一行代码 ( 而不是主进程先运行代码,等待子进程运行结束 ) )

    1、单个子进程:主进程在等待子进程运行完毕之后,在运行p.join()之后的代码。

from multiprocessing import Process
import time

def task(x):
    print('%s is run....' % x)
    time.sleep(3)
    print('%s is done...' % x)

if __name__ == '__main__':
    p1 = Process(target=task, args=('子进程',))
    p1.start()
    p1.join()    # 等待"p.start()"运行完毕,也就是主进程在等待子进程运行完毕之后,在运行下一行代码。
    print('主进程')

>>:子进程 is run....
>>:子进程 is done...
>>:主进程>>:

    2、多个子进程并行执行:主进程在等待所有子进程并行运行完毕之后,在运行p.join()之后的代码。

from multiprocessing import Process
import time
import os

def task(x,n):
    print('%s 子进程%s is run....' %(os.getpid(),x))
    time.sleep(n)
    print('%s 子进程%s is done....' % (os.getpid(), x))

if __name__ == '__main__':
    p1=Process(target=task,args=("a",1,))
    p2=Process(target=task,args=("b",2,))
    p3=Process(target=task,args=("c",3,))

    p1.start()
    p2.start()
    p3.start()
    # 同时开启p1,p2,p3三个子进程(3个子进程并行执行)

    p1.join()
    p2.join()
    p3.join()
    # 这里会等待p1,p2,p3三个子进程 都 运行完毕之后,才运行下一行代码。
    print('主进程')

>>:32812 子进程a is run....
>>:32813 子进程b is run....
>>:32814 子进程c is run....
>>:32812 子进程a is done....
>>:32813 子进程b is done....
>>:32814 子进程c is done....
>>:主进程

 

    1.5、进程对象其他相关的属性或方法

    1、自定义进程名称  p.name

from multiprocessing import Process
import time
import os

def task(n):
    print('子进程的PID:%s' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task,args=(3,),name='子进程')    #修改进程的名称为"子进程"
    p.start()
    print(p.name)          # 通过p.name获取进程名称,默认为"Process-1"

>>:子进程
>>:子进程的PID:12244

    2、判断进程是否处于活动状态  p.is_alive()

from multiprocessing import Process
import time
import os

def task(n):
    print('子进程的PID:%s' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task,args=(3,),name='子进程')    # 修改进程的名称为"子进程"
    p.start()

    print(p.is_alive())    # 判断进程是否处于一个活动状态
    print('主')

>>:True
>>:主
>>:子进程的PID:14572

    3、终止子进程  p.terminate()

from multiprocessing import Process
import time
import os

def task(n):
    print('子进程的PID:%s' %os.getpid())

if __name__ == '__main__':
    p=Process(target=task,args=(3,),name='子进程')    # 修改进程的名称为"子进程"
    p.start()

    p.terminate()            # 给操作体统发送指令,把进程释放掉
    time.sleep(1)            # 应用程序不能直接终止掉进程,需要等待操作系统终止进程
    print(p.is_alive())      # 判断进程是否处于一个活动状态
    print('主')

>>:False
>>:主

 

    1.6、进程之间 内存空间相互隔离 (数据不互通)

from multiprocessing import Process

x=100          # 主进程的变量x=100
def task():
    global x
    x=0        # 设置子进程的变量x=0
    print(x)   # 打印子进程的变量x的值

if __name__ == '__main__':
    p=Process(target=task)
    p.start()
    p.join()        # 等待子进程执行完
    print('主',x)   # 主进程的变量x

>>:0              # 这里打印的是task()函数内变量x的值,也就是子进程里面的变量x的值。
>>:主 100          # 主进程的x依旧为100,说明进程之间相互隔离,相互之间的变量不会受到影响。

    

    1.7、守护进程  ★★

    1.7.1、什么是守护进程?  --只守护主进程

        1、守护进程会在主进程代码运行结束的情况下,立即终止,而不会去管其他子进程是否运行完毕。(进程之间是互相独立的,主进程代码运行结束,守护进程随即终止)
        2、守护进程本身就是一个子进程。
        3、主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后,回收子进程的资源(否则会产生僵尸进程),才会结束,
        4、守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

    1.7.2、为什么要用守护进程?

        1、守护进程本身就是一个子进程,所以在主进程需要在任务并发执行的时候开启子进程。
        2、当该子进程执行的任务生命周期伴随着主进程的生命周期时,就需要将该子进程做成守护进程。

    1.7.3、如何使用守护进程?

    1.例子:  这里可看出,当主进程的代码运行完毕后,守护进程就会立马结束掉,而不会去管其他子进程是否运行完毕。

from multiprocessing import Process
import time

def task(x):
    print('%s is running ' %x)
    time.sleep(3)
    print('%s is done' %x)

if __name__ == '__main__':
    p1=Process(target=task,args=('守护进程',))
    p2=Process(target=task,args=('子进程',))

    p1.daemon=True   #一定要在p1.start()前设置p1为守护进程, 禁止p1创建子进程, 并且父进程代码执行结束, p1即终止运行。
    p1.start()
    p2.start()
    print('主')

>>:主                # 这里是主进程的运行,主进程运行完毕后,守护进程就结束了
>>:子进程 is running  # 这里是子进程的运行
>>:子进程 is done

 

    1.8、进程互斥锁 ★★★ (不常用)  互斥锁同时只允许一个进程更改数据

    1.8.1、互斥锁介绍

       1、互斥锁的原理是将 进程/线程 内执行的部分代码由并发执行变成串行执行,牺牲了效率但保证了数据安全。
       2、互斥锁不能连续执行 acquire() 操作,必须等到拿锁的进程释放锁之后(执行了release()操作),其他进程才能拿到该锁。
       3、加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改。
       4、虽然可以用文件共享数据实现进程间通信,但问题是:
         1.效率低(共享数据基于文件,而文件是硬盘上的数据)
         2.需要自己加锁处理

    1.8.2、互斥锁的使用方法

       from multiprocessing import Lock   导入模块
       lock=Lock()      定义锁
       lock.acquire()   加锁
       lock.release()   解锁

     1.8.3、互斥锁的使用例子:文件当数据库,模拟抢票

# 文件db的内容为:{"count":1}
# 注意一定要用双引号,不然json无法识别

from multiprocessing import Process,Lock
import time,json,os

def check():
    dic=json.load(open('db.txt'))
    print('%s 剩余票数%s' %(os.getpid(),dic['count']))

def get():
    dic=json.load(open('db.txt'))
    time.sleep(0.1)       # 模拟读数据的网络延迟
    if dic['count'] >0:
        dic['count']-=1
        time.sleep(0.2)   # 模拟写数据的网络延迟
        json.dump(dic,open('db.txt','w'))
        print('%s 购票成功' %(os.getpid()))

def task(lock):
    check()               # 查看余票不需要加锁

    lock.acquire()       # 加锁 操作
    get()                # 真正开始抢票时需要加锁
    lock.release()       # 解锁 操作

if __name__ == '__main__':
    lock=Lock()            # 定义锁
    for i in range(10):    # 模拟并发10个客户端抢票
        p=Process(target=task,args=(lock,))
        p.start()

>>: 38397 剩余票数1
>>: 38398 剩余票数1
>>: 38399 剩余票数1
>>: 38400 剩余票数1
>>: 38401 剩余票数1
>>: 38402 剩余票数1
>>: 38403 剩余票数1
>>: 38404 剩余票数1
>>: 38405 剩余票数1
>>: 38406 剩余票数1  # 以上进程均可查票
>>: 38397 购票成功   # 但是最终只有一个抢票成功

 

    1.9、进程信号量 ★★★ (不常用)  信号量可以定义锁的数量N,同时只允许N个进程更改数据

    1.9.1、信号量介绍

      信号量是同时允许一定数量的进程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。如果指定信号量为3,那么来一个人获得一把锁,计数加1,当计数等于3时,后面的人均需要等待。一旦释放一把锁后,就有人可以获得一把锁。

      1.9.2、信号量的使用方法

       from multiprocessing import Semaphore   导入模块
       sem=Semaphore(3)                                   实例化一个对象,并定义为信号量的值为3 (一次最多拥有3把锁)
       sem.acquire()     加锁
       sem.release()     解锁

       1.9.3、信号量的使用例子:模拟占位置

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

def go_wc(sem):
    sem.acquire()        # 开启信号量(加锁)
    print('%s 开始 占位' %os.getpid())
    time.sleep(random.randint(1,3))      # 模拟每个人使用的时间不一样
    sem.release()        # 释放信号量(解锁)

if __name__ == '__main__':
    sem=Semaphore(3)     # 设置信号量的值为3(一次最多拥有3把锁)
    for i in range(10):  # 模拟一共10个人
        p=Process(target=go_wc,args=(sem,))
        p.start()

:>> 60829 开始 占坑
:>> 60830 开始 占坑
:>> 60831 开始 占坑
    <----分隔符---->
:>> 60832 开始 占坑
:>> 60833 开始 占坑
    <----分隔符---->
:>> 60834 开始 占坑
:>> 60835 开始 占坑
:>> 60836 开始 占坑
    <----分隔符---->
:>> 60837 开始 占坑
:>> 60838 开始 占坑

 

    1.10、进程间通信 (队列--Queue) ★★★★★

    1.10.1、IPC机制介绍

     进程彼此之间互相隔离,要实现进程间通信(需要用到IPC机制即"队列管道"),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的,一般使用队列

    1.10.2、队列介绍
     队列占用的是内存空间,一般不会无限制的往里面放数据
     队列是用来传递消息的一种介质,所以队列里面存放的数据是比较小的,不应该存在大的文件

     1.10.3、队列的使用方法

      from multiprocessing import Queue   导入模块
      q=Queue(maxsize=(3))                      实例化一个对象,并定义队列的值为3 (里面最多存放3个值,超出后会阻塞)
      q.put({"x":1})     存值
      print(q.get())     取值

     1.10.4、队列的使用例子:存值,取值 (遵循先进先出的原则)

from multiprocessing import Queue

q=Queue(maxsize=(3))  # 实例化一个队列对象q,里面最多存放3个值

q.put({"x":1})        # 往q里面放一个值{"x":1}
q.put("abc")          # 往q里面放一个值"abc"
q.put(123)            # 往q里面放一个值123

print(q.get())        # 取出队列内的值
print(q.get())        # 取出队列内的值
print(q.get())        # 取出队列内的值

:>> {'x': 1}          # 队列遵循先进先出的原则
:>> abc
:>> 123

其他用法:
一、PUT 方法
from multiprocessing import Queue
q=Queue(maxsize=(3))

# 设置block=False,表示不阻塞,当队列满的时候就抛一个异常"queue.Full",提示队列满了
q.put({"x":1},block=False)
q.put("abc",block=False)
q.put(123,block=False)
q.put(345,block=False)

# q.put_nowait 的效果和 q.put(block=False)是一样的。
q.put_nowait(({"x":1}))
q.put_nowait(("abc"))
q.put_nowait((123))
q.put_nowait((345))

# 在block=True时,给一个值设置超时时间,当超出时间未进入到队列时,会提示队列"已满"(抛一个异常"queue.Full")
q.put(1,block=True,timeout=3)
q.put(2,block=True,timeout=3)
q.put(3,block=True,timeout=3)
q.put(4,block=True,timeout=3)

二、GET 方法
from multiprocessing import Queue
q=Queue(maxsize=(3))
# 先添加3个值,把队列打满
q.put(1)
q.put(2)
q.put(3)

# 设置block=False,表示不阻塞,当队列内的值取完的时候就抛一个异常"queue.Empty",提示队列没值了
q.get(block=False)   # 等于 q.get_nowait()
q.get(block=False)   # 等于 q.get_nowait()
q.get(block=False)   # 等于 q.get_nowait()
q.get(block=False)   # 等于 q.get_nowait()

# 在block=True时,给一个值设置超时时间,当超出时间未取到队列内的值时,会提示队列"无值"(抛一个异常"queue.Empty")
q.get(block=True,timeout=3)
q.get(block=True,timeout=3)
q.get(block=True,timeout=3)
q.get(block=True,timeout=3)

 

    1.11、生产者消费者模型 ★★★★★   --在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度

    1.11.1、什么是生产者消费者模式
    生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

    1.11.2、为什么要使用生产者和消费者模式
    在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

    1.11.3、生产者消费者模型总结 :生产者消费者模型实现了程序的解耦和
     1) 程序中有两类角色:
        一类负责生产数据(生产者)
        一类负责处理数据(消费者)

     2) 引入生产者消费者模型为了解决的问题是:
        平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度
     3) 如何实现:
        生产者 <---> 队列 <---> 消费者

     1.11.4、生产者,消费者的使用例子1:生产者生产完毕后,必须手动在队列内发送结束信号"None",否则消费者(取不到结束信号时)会一直等待。 这种方式一般不使用。

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

def consumer(q):
    while True:
        res=q.get()            # 这里消费者等待接受一个"结束"信号。
        if res is None:break   # 拿到结束信号后,跳出循环。
        time.sleep(random.randint(1,3))
        print('\033[45m%s 张三吃了 %s\033[0m' %(os.getpid(),res))

def producer(q):
    for i in range(5):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('\033[44m%s 厨师生产了 %s\033[0m' %(os.getpid(),res))
    # 生产者们都生产完毕之后,需要在队列末尾发送一个结束的信号"None"

if __name__ == '__main__':
    # 队列
    q=Queue()

    #生产者们: 即厨师们
    p1=Process(target=producer,args=(q,))

    #消费者们: 即吃货们
    c1=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    c1.start()
    p1.join()
    q.put(None)
    print('主')

78323 厨师生产了 包子0
78324 张三吃了 包子0
78323 厨师生产了 包子1
78324 张三吃了 包子1
78323 厨师生产了 包子2
78324 张三吃了 包子2
78323 厨师生产了 包子3
78324 张三吃了 包子3
78323 厨师生产了 包子4
主
78324 张三吃了 包子4

 

     1.11.5、生产者,消费者的使用例子2:解决例子1中,消费者消费完毕后代码未结束的问题(使用JoinableQueue模块和开启守护进程的方式)。

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

def consumer(q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('\033[45m%s 张三吃了 %s\033[0m' %(os.getpid(),res))
        q.task_done()   # 这表示消费者已经取到值了,也就是给q.join()发一个取到值的信号。

def producer(q):
    for i in range(5):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('\033[44m%s 厨师生产了 %s\033[0m' %(os.getpid(),res))
    # 生产者们都生产完毕之后,需要在队列末尾发送一个结束的信号"None"

if __name__ == '__main__':
    # 队列
    q=JoinableQueue()

    #生产者们: 即厨师们
    p1=Process(target=producer,args=(q,))
    #消费者们: 即吃货们
    c1=Process(target=consumer,args=(q,))
    c1.daemon=True      # 设置c1为守护进程,也就是随着主进程的运行结束而结束(主进程运行完时,也就是消费者消费完数据的时候)。
                        # 此时消费者的代码还在q.get()等待,需要设置c1为守护进程,随着主进程的运行结束而结束。

    #生产者,消费者开始运行
    p1.start()
    c1.start()

    p1.join()   # 生产者已经全部生产(运行)完毕。
    q.join()    # 这里是等待队列结束[队列内的数据都被取完了(被消费完了),才算结束],也就是生产者和消费者都运行完了。
    print('主') # 主进程运行完毕

75154 厨师生产了 包子0
75154 厨师生产了 包子1
75155 张三吃了 包子0
75155 张三吃了 包子1
75154 厨师生产了 包子2
75155 张三吃了 包子2
75154 厨师生产了 包子3
75155 张三吃了 包子3
75154 厨师生产了 包子4
75155 张三吃了 包子4
主

 

    1.12、进程池 ★★★★★ (子进程不能无限开,每个子进程都会占用系统资源,当请求量过大时,开启过多的子进程会导致内存跑满。需要对开启子进程的数量做一个限制,进程池就是做这个的。)

    1.12.1、什么进程池、线程池
        池指的一个容器,该容器用来存放进程或线程,存放的数目是一定的。

    1.12.2、为什么要用池
        用池是为了将并发的进程或线程数目控制在计算机可承受的范围内。

        1).为何要用进程进池?
            当任务是"计算密集型"的情况下应该用进程来利用多核优势。
        2).为何要用线程进池?
            当任务是"IO密集型"的情况下应该用线程减少开销。

    1.12.3、进程池 提交任务的2种方式:
        1).同步调用
            同步调用:提交完任务后,就在原地等待任务执行完毕,拿到运行结果/返回值后再执行下一行代码。
            同步调用下任务的执行是串行执行。

        1).异步调用
            异步调用:提交完任务后,不会原地等待任务执行完毕,结果??? 会直接执行下一行代码。
            异步调用下任务的执行是并发执行。

    1.12.4、进程池例子:异步调用

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor

import os
import time
import random

def task(x):
    print("%s is running" %os.getpid())
    # time.sleep(random.randint(1,3))
    time.sleep(2)
    return x+1

if __name__ == '__main__':
    start_time=time.time()
    res_list=[]
    p=ProcessPoolExecutor(2)    # 开启进程池(池的大小为2),不指定参数的话,池的大小默认为CPU核数的大小。
    for n in range(5):
        res=p.submit(task, n)   # 发送指令,准备开始执行进程池内的代码(指定对应的参数,task是要执行的函数,111是传给task的参数)
        # print(type(res))      # res是拿到进程池内代码的返回对象,也就是'concurrent.futures._base.Future'类的实例化。
        res_list.append(res)

    print("主")
    p.shutdown(wait=True)       # p.shutdown表示:关闭进程池入口,wait=True表示:等待进程池内的进程运行完毕(相当于进程的join()操作)。
                                # 加上这步操作表示:等待进程池内的代码全部都运行完毕。

    for i in res_list:
        print(i.result())       # 调用'concurrent.futures._base.Future'类下的result方法,拿到返回结果(进程池内代码的返回结果)。
                                # 然后这里在拿值就相当于异步调用(p.shutdown(wait=True)已经把所有的代码都执行完毕了)。
    print(time.time()-start_time)  # 6秒

# 主
# 26968 is running
# 26969 is running
# 26969 is running
# 26968 is running
# 26969 is running
# 1
# 2
# 3
# 4
# 5

 

    1.12.5、进程池例子:同步调用

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor

import os
import time
import random

def task(x):
    print("%s is running" %os.getpid())
    # time.sleep(random.randint(1,3))
    time.sleep(2)
    return x+1

if __name__ == '__main__':
    start_time = time.time()
    p=ProcessPoolExecutor(2)    # 开启进程池(池的大小为2),不指定参数的话,池的大小默认为CPU核数的大小。
    for n in range(5):
        res=p.submit(task, n)   # 发送指令,准备开始执行进程池内的代码(指定对应的参数,task是要执行的函数,111是传给task的参数)
        print(res.result())     # 等待进程N执行完毕,通过result()拿到task函数的返回结果。
        # res=p.submit(task, n).result()  # 可以2步并作一步。

    p.shutdown(wait=True)       # p.shutdown表示:关闭进程池入口,wait=True表示:等待进程池内的进程运行完毕(相当于进程的join()操作)。
    print(time.time()-start_time) # 10秒

# 50630 is running
# 1
# 50631 is running
# 2
# 50630 is running
# 3
# 50631 is running
# 4
# 50630 is running
# 5

 

    1.12.6、进程池例子:回调函数(一般与异步调用一起使用的),好处:解耦合,同时不会额外增加消耗时间

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import os
import time
import requests

def get(url):
    print('%s GET %s' %(os.getpid(),url))
    time.sleep(2)
    response=requests.get(url)
    if response.status_code == 200:
        res=response.text
        return res

def parse(res):
    print('%s 解析[url]结果是 %s' % (os.getpid(), len(res.result())))

if __name__ == '__main__':
    start_time=time.time()
    p=ProcessPoolExecutor(2)
    urls=[
        'http://www.baidu.com',
        'http://www.baidu.com',
        'http://www.baidu.com',
        'http://www.baidu.com',
        'http://www.baidu.com',
    ]
    for url in urls:
        p.submit(get,url).add_done_callback(parse)  # 回调函数(在一个任务(进程内的代码)运行完毕并且"有返回值"的时候自动触发,也就是把(进程内的代码)的返回值自动传参给了"parse(res)函数里面的res参数",同时"parse(res)函数"里面接收的参数只能写一个。)
                                                    # 执行p.submit(get,url)相当于实例化'concurrent.futures._base.Future'类,然后执行Future类下的add_done_callback()方法
    print("主",os.getpid())        # 主进程去调用parse(res)函数(也就是主进程做了解析的活)。
                                   # 而进程池内的子进程都去调用get(url)函数了。
    p.shutdown(wait=True)          # 等待进程池内代码运行完毕。
    print(time.time()-start_time)

# 主 18901
# 18902 GET http://www.baidu.com
# 18903 GET http://www.baidu.com
# 18902 GET http://www.baidu.com
# 18901 解析[url]结果是 2381
# 18903 GET http://www.baidu.com
# 18901 解析[url]结果是 2381
# 18902 GET http://www.baidu.com
# 18901 解析[url]结果是 2381
# 18901 解析[url]结果是 2381
# 18901 解析[url]结果是 2381
# 6.110453844070435

 

 

二、线程的使用

    2.1、如何使用线程(开启进程的2种方式)

  方式一:调用类 Thread 并实例化对象执行 --常用方法 

from threading import Thread
import time

def task(name):
    print('%s is start' %name)
    time.sleep(3)
    print('%s is end' %name)

if __name__ == '__main__':
    t=Thread(target=task,args=('sudada',))
    t.start()          #线程开启的速度非常快,几乎在向操作系统发送开启线程指令时,线程就已被开启
    # t.join()
    print('主')

>>:sudada is start    #这里看到在运行t.start()之后,立马就打印了'sudada is start'
>>:主
>>:sudada is end

  方式二:自定义类 MyThread 然后继承 Thread 类的方式,MyThread类里面必须要创建一个run()函数 [ 这个函数内容同"方法一"里面的task(name)函数内容一样,即子线程开启后运行的函数,也就是'方法一'里面Thread(target="函数名") ]

from threading import Thread
import time,random

class Mythread(Thread):
    def run(self):
        print('%s is start' %self.name)
        time.sleep(random.randint(1,3))
        print('%s is end' %self.name)

if __name__ == '__main__':
    t=Mythread()
    t.start()
    print('主')
    
>>:Thread-1 is start
>>:主
>>:Thread-1 is end

 

    2.2、同一进程下的多个线程,共享该进程内的数据

from threading import Thread

x=100          # 主线程的变量x=100
def task():
    global x
    x=0        # 设置子线程的变量x=0
    print(x)   # 打印子线程的变量x的值

if __name__ == '__main__':
    p=Thread(target=task)
    p.start()
    p.join()        # 等待子线程执行完
    print('主',x)   # 主线程的变量x

>>:0              # 这里打印的是task()函数内变量x的值,也就是子线程里面的变量x的值。
>>:主 0            # 主线程的x也为为0,说明线程之间数据共享,相互之间的变量共有。

 

    2.3、线程相关的其他属性方法

    1、判断线程是否处于活动状态  t.is_alive()

from threading import Thread

def task(name):
    print('%s is running ' %name)

if __name__ == '__main__':
    t=Thread(target=task,args=('sudada',))
    t.start()
    print(t.is_alive())   # 线程开启后,判断线程是否存活(由进程(操作系统)控制线程的回收,所以此处是否被回收取决于进程(操作系统)的回收状态)
    t.join()
    print(t.is_alive())   # 主线程等待子线程运行完毕后查看是否存活,这里一定为"False"
    print('主')

>>:sudada is running 
>>:False
>>:False
>>:主

    2、自定义线程名称 和 获取线程名称:

    方式一  t.setName('线程1') 对应 t.getName() 

    方式二  t=Thread(target=task,args=('sudada',),name="线程1")  对应 t.name

    特殊:current_thread().getName()  这个拿到的是当前子线程的线程对象的名称 (不是主线程的名称),一般在测试会用到。

from threading import Thread,current_thread

def task(name):
    print('%s is running ' %name)
    # print('%s is running ' %current_thread().getName())  # current_thread().getName()  直接获取子线程默认名称

if __name__ == '__main__':
    t=Thread(target=task,args=('sudada',))
    # t=Thread(target=task,args=('sudada',),name="线程1")  # 方法一(在主线程下)设置子线程名称为"线程1"
    t.start()
    t.join()
    print('主')
    print(t.name)       # 方法一(在主线程下)获取子线程名称:"线程1"

    t.setName('线程1')   # 方法二(在主线程下)设置子线程名称为"线程1"
    print(t.getName())  # 方法二(在主线程下)获取子线程名称:"线程1"

>>:Thread-1  # 这是系统默认的线程名
>>:线程1      # 这是自定义的线程名

    3、一个进程内 所有线程的PID相同

from threading import Thread,current_thread
import os

def task():
    print('%s is running,pid is:%s ' %(current_thread().getName(),os.getpid()))

if __name__ == '__main__':
    t1=Thread(target=task,)
    t2=Thread(target=task,)
    t3=Thread(target=task,)
    t1.start()
    t2.start()
    t3.start()
    print('主',os.getpid())

>>:Thread-1 is running,pid is:4540
>>:Thread-2 is running,pid is:4540
>>:Thread-3 is running,pid is:4540
>>:主 4540

    4、线程并发开启  (主线程需要等待子线程全部运行结束后,才能释放掉子线程所占用的资源。主线程代表了一个进程的生命周期,而一个进程一定要等到内部包含的所有线程都运行结束后,才能释放资源)

from threading import Thread,current_thread
import os,time

def task(n):
    print('%s is running,pid is:%s ' %(current_thread().getName(),os.getpid()))
    time.sleep(n)

if __name__ == '__main__':
    t1=Thread(target=task,args=(1,))    #第一个线程等待1秒
    t2=Thread(target=task,args=(2,))    #第二个线程等待2秒
    t3=Thread(target=task,args=(3,))    #第三个线程等待3秒

    start=time.time()
    t1.start()   #开启线程
    t2.start()   #开启线程
    t3.start()   #开启线程

    t1.join()    #等待线程运行结束
    t2.join()    #等待线程运行结束
    t3.join()    #等待线程运行结束

    print('主',os.getpid())
    print(time.time() - start)

>>:Thread-1 is running,pid is:10824 
>>:Thread-2 is running,pid is:10824 
>>:Thread-3 is running,pid is:10824 
>>:主 10824
>>:3.001558780670166      #开启3个线程耗费的时间

 

    2.4、守护线程 ★★

    2.4.1、什么是守护线程?  --守护进程内所有的线程

        1、守护线程会在 "该进程内所有非守护线程全部都运行完毕后, 守护线程才会结束" 。并不是在主线程运行完毕后守护线程挂掉。这一点是和守护进程的区别之处!

        2、守护线程守护的是:当前进程内所有的子线程!

        3、主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

    2.4.2、如何使用守护线程?

        情况一:只有一个子线程并且为守护线程时,那么这个守护线程就会等待主线程运行完毕后结束。

from threading import Thread
import os
import time

def task(x):
    print('%s is running ' %x)
    time.sleep(3)
    print('%s is done' %x)

if __name__ == '__main__':
    t1=Thread(target=task,args=('守护线程',))
    t1.daemon=True     # 设置t1为守护线程,必须在t1.start()设定
    t1.start()
    print('主')

>>:守护线程 is running  # 运行守护线程
>>:主                  # 运行主线程,主线程运行完毕后,守护线程结束,进程也结束了。

        情况二开启多个子线程时,守护线程在等待所有的子线程运行完毕后,守护线程就会立刻结束 ( 备注:如果守护线程的代码先执行完,那么守护线程会先结束。如果守护线程执行了很久还没完,但是此时其他子线程都执行完了,那么守护线程会立刻结束 )。

from threading import Thread
import time
def foo():
    print(123)
    time.sleep(3)
    print("end123")

def bar():
    print(456)
    time.sleep(1)
    print("end456")

def abc():
    print(789)
    time.sleep(1)
    print("end789")

t1=Thread(target=foo)
t2=Thread(target=bar)
t3=Thread(target=abc)

t1.daemon=True
t1.start()
t2.start()
t3.start()
print("main-------")

>>:123    # 守护线程
>>:456    # 子线程t2
>>:789    # 子线程t3
>>:main-------   # 主线程
>>:end456        # 子线程t2
>>:end789        # 子线程t3

 

    2.5、线程互斥锁 ★★★ (不常用)  互斥锁同时只允许一个线程更改数据,也就是部分代码并行,部分代码串行。

    2.5.1、互斥锁介绍

       1、互斥锁的原理是将 进程/线程 内执行的部分代码由并发执行变成串行执行,牺牲了效率但保证了数据安全。
       2、互斥锁不能连续执行 acquire() 操作,必须等到拿锁的线程释放锁之后(执行了release()操作),其他线程才能拿到该锁。
       3、加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改。
       4、虽然可以用文件共享数据实现进程间通信,但问题是:
         1.效率低(共享数据基于文件,而文件是硬盘上的数据)
         2.需要自己加锁处理

    2.5.2、互斥锁的使用方法

       from multiprocessing import Lock   导入模块
       lock=Lock()      定义锁
       lock.acquire()   加锁
       lock.release()   解锁

     2.5.3、互斥锁的使用例子:开启多个线程修改一个变量的值,通过加锁的方式

from threading import Thread,Lock
import time
a=50  # 设置一个变量值为50
def task():
    global a
    print(a)
    lock.acquire()   # 加锁
    temp=a
    time.sleep(0.1)  # 等待0.1秒,这个等待时间内,操作系统会生成50个线程,同时这50个线程都会执行join()操作。
    a=temp-1         # 变量值减1
    lock.release()   # 解锁

if __name__ == '__main__':
    lock = Lock()
    t_list=[]
    start_time=time.time()
    for n in range(50):
        t=Thread(target=task)
        t_list.append(t)
        t.start()    # 开启线程,这里不能使用t.join(),使用的话就相当于一个个操作了,不是并发的效果。

    for n in t_list: # 在sleep(0.1)的时候,这一行代码已经运行完毕了。
        n.join()     # 等待线程执行完毕,线程执行完毕后a的值就会被减1

    print(a)         # 最后打印变量a的值
    print(time.time()-start_time)

0
5.1949992179870605

    2.6、线程信号量 ★★★ (不常用)  信号量可以定义锁的数量N,同时只允许N个线程更改数据

    2.6.1、信号量介绍

      信号量是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。如果指定信号量为3,那么来一个人获得一把锁,计数加1,当计数等于3时,后面的人均需要等待。一旦释放一把锁后,就有人可以获得一把锁。

      2.6.2、信号量的使用方法

       from multiprocessing import Semaphore   导入模块
       sem=Semaphore(3)                                   实例化一个对象,并定义为信号量的值为3 (一次最多拥有3把锁)
       sem.acquire()     加锁
       sem.release()     解锁

       2.6.3、信号量的使用例子:模拟占位置

from multiprocessing import Process,Semaphore
from threading import Thread
import time,random,os

def go_wc(sem):
    sem.acquire()        # 开启信号量(加锁)
    print('%s 开始 占位' %os.getpid())
    time.sleep(random.randint(1,3))      # 模拟每个人使用的时间不一样
    sem.release()        # 释放信号量(解锁)

if __name__ == '__main__':
    sem=Semaphore(3)     # 设置信号量的值为3(一次最多拥有3把锁)
    for i in range(10):  # 模拟一共10个人
        t=Thread(target=go_wc,args=(sem,))
        t.start()

7893 开始 占位
7893 开始 占位
7893 开始 占位
<----分隔符---->
7893 开始 占位
7893 开始 占位
<----分隔符---->
7893 开始 占位
<----分隔符---->
7893 开始 占位
7893 开始 占位
<----分隔符---->
7893 开始 占位
<----分隔符---->
7893 开始 占位

    2.7、GIL(全局解释器锁) ★★★★★

    2.7.1、GIL是什么?
    GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
每启动一个进程,该进程就会有一个GIL锁,用来控制该进程内的多个线程同一时间只有一个线程被执行,这意味着cpython解释器的多线程没有并行的效果,但是有并发的效果。
GIL是cpython解释器的特许,而不是python的特性。

    2.7.2、GIL和自定义互斥锁的区别
    (每一个进程内都有一个GIL)在一个进程内的多个线程要想执行代码,首先需要抢的是GIL,GIL就相当于执行权限。

    抢到GIL的那个线程A会去自定义一个互斥锁(GIL+自定义锁相当于2把锁,此时线程A就有2把锁),拿到这个自定义的锁之后,线程A就可以执行自定义锁内的代码。而其他的线程BC都去等待抢GIL(线程A执行完毕后释放GIL,或者操作系统强制让线程A释放GIL),此时就算操作系统强制把线程A的GIL释放掉,然后任由其他线程BC抢到了GIL,但是自定义锁还是被线程A拿在手里,这时候线程BC也无法执行代码(线程A已经把自定义锁拿走了),所以线程BC的GIL又被操作系统回收了,然后线程A加入到抢GIL锁里面,直到线程A抢到了GIL,然后执行未执行完毕的代码。然后释放自定义锁,然后释放GIL。然后线程BC再去抢GIL,再去自定义锁并执行代码。

    2.7.3、GIL与多线程
    有了GIL的存在,同一时刻同一进程中只有一个线程被执行。听到这里,有的同学立马质问:进程可以利用多核,但是开销大,而python的多线程开销小,但却无法利用多核优势,也就是说python没用了?
    一、要解决这个问题,我们需要在几个点上达成一致:
     1.cpu到底是用来做计算的,还是用来做I/O的?
     2.多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能。
     3.每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处。

    二、结论:
     对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
     当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地。

    三、分析:
    我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
    方案一:开启四个进程
    方案二:一个进程下,开启四个线程

    单核情况下,分析结果: 
  如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
  如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜

    多核情况下,分析结果:
  如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
  如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

    四、结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

    五、也就是说:
    python的多进程用于计算密集型
    python的多线程用于IO密集型

    2.7.3、GIL与多线程
    1、程序属于"计算密集型"时:多进程效率高

# 计算密集型:多进程效率高
from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i
    print(res)

if __name__ == '__main__':
    l=[]
    print(os.cpu_count())          #本机为4核
    start=time.time()
    for i in range(4):
        p=Process(target=work)     #耗时16s多
        # p=Thread(target=work)    #耗时21s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

    2、程序属于"I/O密集型"时:多线程效率高

# I/O密集型:多线程效率高
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    print('===>')

if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        # p=Process(target=work) #耗时3.5s多,大部分时间耗费在创建进程上
        p=Thread(target=work) #耗时2s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

 

    2.8、死锁现象与递归锁 ★★

    2.8.1、死锁现象,例子如下

from threading import Thread,Lock,RLock
import time

mutexA=Lock()
mutexB=Lock()

class Mythread(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()
        print('%s 抢到了A锁' %self.name)

        mutexB.acquire()
        print('%s 抢到了B锁' %self.name)
        mutexB.release()

        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print('%s 抢到了B锁' %self.name)
        time.sleep(1)

        mutexA.acquire()
        print('%s 抢到了A锁' %self.name)
        mutexA.release()

        mutexB.release()

if __name__ == '__main__':
    for i in range(10):
        t=Mythread()
        t.start()

 

    2.8.2、递归锁,解决死锁问题

from threading import Thread,Lock,RLock
import time

# 递归锁可以连续acquire()
# 这里表示mutexA和mutexB是同一把锁
mutexB=mutexA=RLock()     

class Mythread(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()  # 线程N抢到锁后做一次+1的操作。0+1=1
        print('%s 抢到了A锁' %self.name)

        mutexB.acquire()  # 线程N抢到锁后做一次+1的操作。1+1=2
        print('%s 抢到了B锁' %self.name)
        mutexB.release()  # 线程N抢到锁后做一次-1的操作。2-1=1

        mutexA.release()  # 线程N抢到锁后做一次-1的操作。1-1=0

    def f2(self):
        mutexB.acquire()  # 到这一步时,所有的线程一起抢这个锁。
        print('%s 抢到了B锁' %self.name)
        time.sleep(1)

        mutexA.acquire()
        print('%s 抢到了A锁' %self.name)
        mutexA.release()

        mutexB.release()

if __name__ == '__main__':
    for i in range(5):
        t=Mythread()
        t.start()

 

    2.9、线程queue ★★★★★

    2.9.1、队列:先进先出

import queue

q=queue.Queue()
q.put(1)
q.put(2)
q.put(3)

print(q.get())
print(q.get())
print(q.get())

 

    2.9.2、堆栈:先进后出

q=queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)

print(q.get())
print(q.get())
print(q.get())

 

    2.9.3、优先级队列:优先级高的优先出(数字代表优先级,数字越小优先级越高)

import queue

q=queue.PriorityQueue()
q.put((10,"a"))
q.put((9,"a"))
q.put((15,"a"))

print(q.get())
print(q.get())
print(q.get())

:>> (9, 'a')
:>> (10, 'a')
:>> (15, 'a')

 

    2.10、线程池 ★★★★★

    2.10.1、什么进程池、线程池
        池指的一个容器,该容器用来存放进程或线程,存放的数目是一定的。

    2.10.2、为什么要用池
        用池是为了将并发的进程或线程数目控制在计算机可承受的范围内。

        1).为何要用进程进池?
            当任务是"计算密集型"的情况下应该用进程来利用多核优势。
        2).为何要用线程进池?
            当任务是"IO密集型"的情况下应该用线程减少开销。

    2.10.3、线程池 提交任务的2种方式:
        1).同步调用
            同步调用:提交完任务后,就在原地等待任务执行完毕,拿到运行结果/返回值后再执行下一行代码。
            同步调用下任务的执行是串行执行。

        1).异步调用
            异步调用:提交完任务后,不会原地等待任务执行完毕,结果??? 会直接执行下一行代码。
            异步调用下任务的执行是并发执行。

    2.10.4、线程池例子:异步调用

from concurrent.futures import ThreadPoolExecutor
from threading import current_thread
import os
import time

def task(x):
    print("%s is running" %current_thread().getName())
    time.sleep(2)
    return x+1

if __name__ == '__main__':
    start_time=time.time()
    res_list=[]
    p=ThreadPoolExecutor(2)    # 开启进程池(池的大小为2),不指定参数的话,池的大小默认为CPU核数的大小。
    for n in range(5):
        res=p.submit(task, n)   # 发送指令,准备开始执行进程池内的代码(指定对应的参数,task是要执行的函数,111是传给task的参数)
                                # res是拿到进程池内代码的返回对象,也就是'concurrent.futures._base.Future'类的实例化。
        res_list.append(res)

    print("主",os.getpid())
    p.shutdown(wait=True)       # p.shutdown表示:关闭进程池入口,wait=True表示:等待进程池内的进程运行完毕(相当于进程的join()操作)。
                                # 加上这步操作表示:等待进程池内的代码全部都运行完毕。
    for i in res_list:
        print(i.result())       # 调用'concurrent.futures._base.Future'类下的result方法,拿到返回结果(进程池内代码的返回结果)。
                                # 然后这里在拿值就相当于异步调用(p.shutdown(wait=True)已经把所有的代码都执行完毕了)。
    print(time.time()-start_time)  # 6.0095179080963135

 

 

    2.10.5、线程池例子:同步调用

from concurrent.futures import ThreadPoolExecutor
from threading import current_thread
import os
import time

def task(x):
    print("%s is running" %current_thread().getName())
    time.sleep(2)
    return x+1

if __name__ == '__main__':
    start_time=time.time()
    p=ThreadPoolExecutor(2)    # 开启进程池(池的大小为2),不指定参数的话,池的大小默认为CPU核数的大小。
    for n in range(5):
        res=p.submit(task, n).result()                        # res是拿到进程池内代码的返回对象,也就是'concurrent.futures._base.Future'类的实例化。
        print(res)

    print("主")
    p.shutdown(wait=True)       # p.shutdown表示:关闭进程池入口,wait=True表示:等待进程池内的进程运行完毕(相当于进程的join()操作)。
                                # 加上这步操作表示:等待进程池内的代码全部都运行完毕。
    print(time.time()-start_time)  # 10.017699956893921

 

 

  2.10.6、线程池例子:回调函数(一般与异步调用一起使用的),好处:解耦合,同时不会额外增加消耗时间

from concurrent.futures import ThreadPoolExecutor
from threading import current_thread
import time

def task(x):
    print('%s is running' %current_thread().getName())
    time.sleep(2)
    return x**2

def parse(obj):
    res=obj.result()
    print('%s 解析的结果为%s' %(current_thread().getName(),res))

if __name__ == '__main__':
    start_time=time.time()
    t=ThreadPoolExecutor(2)  # 开启线程池(池的大小为2),不指定参数的话,池的大小默认为CPU核数*5。

    for i in range(5):
        t.submit(task,i).add_done_callback(parse)  # 线程的回调函数

    t.shutdown(wait=True)            # 线程内是各个子线程同时负责下载和解析
    print(time.time()-start_time)

# ThreadPoolExecutor-0_0 is running
# ThreadPoolExecutor-0_1 is running
# ThreadPoolExecutor-0_1 解析的结果为1
# ThreadPoolExecutor-0_1 is running
# ThreadPoolExecutor-0_0 解析的结果为0
# ThreadPoolExecutor-0_0 is running
# ThreadPoolExecutor-0_1 解析的结果为4
# ThreadPoolExecutor-0_1 is running
# ThreadPoolExecutor-0_0 解析的结果为9
# ThreadPoolExecutor-0_1 解析的结果为16

 

 

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值