python 线程的应用

进程、线程与协程

线程

线程是程序内可以直接被CPU调度的执行过程.是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.

线程的创建

python中,我们一般使用Threading模块来创建多线程

  1. 使用theading.Thread类创建线程
from threading import Thread


# 创建任务
def func(name, num_id):
    print(f'{name}是第{num_id}个线程')


if __name__ == '__main__':
    # 创建了线程   现在有三个线程,一个主线程,两个子线程
    thread1 = Thread(target=func, args=("线程1", 1))
    thread2 = Thread(target=func, args=("线程2", 2))
    # 运行线程
    thread1.start()
    thread2.start()
    # 等待线程结束
    thread1.join()
    thread2.join()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1AQ4o75-1690289826340)(D:\大四\面试\md图片\image-20230722155535353.png)]

  1. 通过继承threading.Thread类创建线程
from threading import Thread


# 自定义线程类
class MyThread(Thread):
    # 初始化方法
    def __init__(self, name, num_id):
        super().__init__()
        self.name = name
        self.num_id = num_id

    # 重构run方法
    def run(self):
        print(f'{self.name}是第{self.num_id}个线程')


if __name__ == '__main__':
    t1 = MyThread('线程1', 1)
    t2 = MyThread('线程2', 2)
    # 运行线程(调用run方法)
    t1.start()
    t2.start()
    # 等待线程结束
    t1.join()
    t2.join()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEzOeCtF-1690289826340)(D:\大四\面试\md图片\image-20230722155535353.png)]

Thread类的一些方法

通过上面的代码,我们已经简单掌握了多线程的创建,接下来我们来看一下Thread的一些常用方法

join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

setDaemon(True):将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦

run():  线程被cpu调度后自动执行线程对象的run方法
start():启动线程活动。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。

threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

守护线程

我们先看一段代码

import time
from threading import Thread


def func1(name):
    print(f'{name}线程开始')
    time.sleep(2)
    print(f'{name}线程结束')


def func2(name):
    print(f'{name}线程开始')
    time.sleep(4)
    print(f'{name}线程结束')


if __name__ == '__main__':
    print('主线程开始'.center(50, '-'))
    # 如果只有一个参数,要保证传入的是一个元组,所以得打一个逗号
    t1 = Thread(target=func1, args=(1,))  
    t2 = Thread(target=func2, args=(2,))
    # 运行线程
    t1.start()
    t2.start()
    print('主线程结束'.center(50, '-'))

结果:

----------------------主线程开始-----------------------
1线程开始
2线程开始
----------------------主线程结束-----------------------
1线程结束
2线程结束

进程已结束,退出代码0

我们可以看到主线程的print并不是等t1,t2线程都执行完毕之后才打印的,这是因为主线程和t1,t2 线程是同时跑的。但是主进程要等非守护子线程结束之后,主线程才会退出。

一般开发中,我们需要主线程的print打印是在最后面的,表明所有流程都结束了,也就是主线程结束了。这里就引入了一个join的概念。

if __name__ == '__main__':
    print('主线程开始'.center(50, '-'))
    t1 = Thread(target=func1, args=(1,))  # 如果只有一个参数,要保证传入的是一个元组,所以得打一个逗号
    t2 = Thread(target=func2, args=(2,))
    # 运行线程
    t1.start()
    t2.start()
    
    t1.join()
    t2.join()
    print('主线程结束'.center(50, '-'))

结果:

----------------------主线程开始-----------------------
1线程开始
2线程开始
1线程结束
2线程结束
----------------------主线程结束-----------------------

进程已结束,退出代码0

上面的情况,主进程都需要等待非守护子线程结束之后,主线程才结束。那我们是不是注意到一点,我说的是“非守护子线程”,那什么是非守护子线程?默认的子线程都是主线程的非守护子线程,但是有时候我们有需求,当主进程结束,不管子线程有没有结束,子线程都要跟随主线程一起退出,这时候我们引入一个“守护线程”的概念。

如果某个子线程设置为守护线程,主线程其实就不用管这个子线程了,当所有其他非守护线程结束,主线程就会退出,而守护线程将和主线程一起退出,守护主线程,这就是守护线程的意思

if __name__ == '__main__':
    print('主线程开始'.center(50, '-'))
    t1 = Thread(target=func1, args=(1,))  # 如果只有一个参数,要保证传入的是一个元组,所以得打一个逗号
    t2 = Thread(target=func2, args=(2,))
    # 运行线程
    t1.start()
    t2.setDaemon(True)
    t2.start()

    print('主线程结束'.center(50, '-'))

结果:

----------------------主线程开始-----------------------
1线程开始
2线程开始
----------------------主线程结束-----------------------
1线程结束

进程已结束,退出代码0

这里设置t2为Daemon,那么主线程就不管t2的运行状态,只管等待t1结束, t1结束主线程就结束了
因为t2的时间4秒,t1的时间2秒, 主线程在等待t1线程结束的过程中, t2线程自己结束

GIL锁

Python中,全局解释器锁GIL本质上是一个互斥锁,它是在解释器CPython层面上的锁。Python语言设计之初,计算机广泛使用的还是单核CPU,为了解决多线程之间的数据完整与状态同步问题,最简单的方法就是加锁,每个线程在运行前都需要获取一把锁,从而保证同一时刻只能有一个线程运行,这把锁就是全局解释器锁。

GIL确保了一个进程中同一时刻只有一个线程运行,多线程在实际运行中只调用了一个CPU核心,无法使用多个CPU核心,因此多线程不能在多个CPU核心上并行,面对计算密集型的操作时,无法利用CPU的多核优势,运行效率较低。但是GIL对于单线程及I/O密集型的多线程运行没有影响。Python中的每个进程都有自己的解释器,因此,多进程不受GIL的限制,可以在CPU的多个核心上并行,多进程适合计算密集型的操作,但是进程的开销较大,不适用于I/O密集型的操作。GIL不是Python的缺陷,只是一种针对解释器的设计思想,其中使用GIL的解释器有CPython,未使用GIL的解释器有JPython

并发与并行

并发:同一时间段处理多个任务

并行:同时处理多个任务

线程是并发还是并行?进程是并发还是并行

在宏观层面,并发是指多个任务在同一时间段内完成,并行是指多个任务在同一时间点完成。因此,并发不强调同时性,而并行强调同时性。

在微观层面,并发通常是多个任务交由一个CPU进行处理,CPU将任务进行处理的时间划分为非常细小的间隔,CPU不断地轮询这些任务,每个任务每次执行一个时间间隔,因此,看起来这些任务是在同时处理。并行是指多个任务交由多个CPU进行处理,每个任务单独使用一个CPU,彼此之间相互独立,互不影响,可以同时进行处理。

在Python中由于GIL的存在,同一时刻一个进程中只能有一个线程运行,多个线程只能在一个CPU核心上交替运行,因此多线程是并发。进程不受GIL的限制,多个进程可以在多个CPU核心中同时运行,因此多进程是并行。

线程的同步机制

在多线程编程中,每个线程之间是相互独立的,不会用到同享的资源或者数据,如果我们多个线程要用到相同的数据,那么就会存在资源争用的问题

首先先看一下资源争用的情况

import threading
import time

num = 100


def fun_sub():
    global num
    num2 = num
    time.sleep(0.001)
    num = num2 - 1


if __name__ == '__main__':
    print('开始测试同步锁 at %s' % time.ctime())

    thread_list = []
    for thread in range(100):
        t = threading.Thread(target=fun_sub)
        t.start()
        thread_list.append(t)

    for t in thread_list:
        t.join()
    print('num is %d' % num)
    print('结束测试同步锁 at %s' % time.ctime())

创建100的线程,然后每个线程去从公共资源 num 变量去执行减1操作,按照正常情况下面,等到代码执行结束,打印 num 变量,应该得到的是0,因为100个线程都去执行了一次减1的操作。

开始测试同步锁 at Sat Jul 22 20:22:26 2023
num is 98
结束测试同步锁 at Sat Jul 22 20:22:26 2023

我们会发现,每次执行的结果num值都不是一样的,上面显示的是91,那就存在问题了,为什么结果不是0呢?

我们来看看上面代码的执行流程。
1.因为GIL,只有一个线程(假设线程1)拿到了num这个资源,然后把变量赋值给num2,sleep 0.001秒,这时候num=100
2.当第一个线程sleep 0.001秒这个期间,这个线程会做yield操作,就是把cpu切换给别的线程执行(假设线程2拿到个GIL,获得cpu使用权),线程2也和线程1一样也拿到num,返回赋值给num2,然sleep,这时候,其实num还是=100.
3.线程2 sleep时候,又要yield操作,假设线程3拿到num,执行上面的操作,其实num有可能还是100
4.等到后面cpu重新切换给线程1,线程2,线程3上执行的时候,他们执行减1操作后,其实等到的num其实都是99,而不是顺序递减的。
5.其他剩余的线程操作如上

为了解决资源争用的问题,可以使用以下机制解决

锁机制
同步锁
import threading
import time

num = 100


def fun_sub():
    global num
    lock.acquire()
    print('----加锁----')
    print('现在操作共享资源的线程名字是:', t.name)
    num2 = num
    time.sleep(0.001)
    num = num2 - 1
    lock.release()
    print('----释放锁----')


if __name__ == '__main__':
    print('开始测试同步锁 at %s' % time.ctime())

    lock = threading.Lock()  # 创建一把同步锁

    thread_list = []
    for thread in range(10):
        t = threading.Thread(target=fun_sub)
        thread_list.append(t)
        t.start()

    for t in thread_list:
        t.join()
    print('num is %d' % num)
    print('结束测试同步锁 at %s' % time.ctime())
    print(thread_list)

结果:

D:\python\Python38\python.exe D:\大四\面试\main.py 
开始测试同步锁 at Sat Jul 22 20:38:57 2023
----加锁----
现在操作共享资源的线程名字是: Thread-1
----释放锁--------加锁----

现在操作共享资源的线程名字是: Thread-1
----释放锁--------加锁----
现在操作共享资源的线程名字是: 
Thread-2
----释放锁--------加锁----

现在操作共享资源的线程名字是: Thread-3
----释放锁--------加锁----
现在操作共享资源的线程名字是: 
Thread-4
----释放锁--------加锁----

现在操作共享资源的线程名字是: Thread-5
----释放锁----
----加锁----
现在操作共享资源的线程名字是: Thread-7
----释放锁--------加锁----
现在操作共享资源的线程名字是: 
Thread-7
----释放锁--------加锁----
现在操作共享资源的线程名字是: 
Thread-8
----释放锁--------加锁----
现在操作共享资源的线程名字是: Thread-9

----释放锁----
num is 90
结束测试同步锁 at Sat Jul 22 20:38:57 2023
[<Thread(Thread-1, stopped 7192)>, <Thread(Thread-2, stopped 14280)>, <Thread(Thread-3, stopped 39340)>, <Thread(Thread-4, stopped 24428)>, <Thread(Thread-5, stopped 9392)>, <Thread(Thread-6, stopped 35728)>, <Thread(Thread-7, stopped 21064)>, <Thread(Thread-8, stopped 28264)>, <Thread(Thread-9, stopped 25024)>, <Thread(Thread-10, stopped 9960)>]

进程已结束,退出代码0

<Thread(Thread-1, stopped 7192)> (名称,状态,标识符)

至于 线程名称为什么会重复,以下是GPT的解释:

当多个线程在操作系统中运行时,操作系统的调度机制和全局解释器锁 (GIL) 都会对线程的执行顺序和执行时间片进行调整。下面是一个简单的例子来说明它们的影响:

假设有两个线程:Thread-1和Thread-2,它们都在执行关键部分的代码之前获取到了锁。

Thread-1获取到锁并开始执行关键部分的代码。然而,在执行一段时间后,操作系统的调度器决定将执行权切换给Thread-2,这可能是根据调度算法、线程优先级或其他策略决定的。

Thread-1执行一段时间
线程切换到Thread-2
Thread-2获取到锁,并开始执行关键部分的代码。然而,如果存在全局解释器锁 (GIL),在某些Python解释器中,只有一个线程可以执行Python字节码。

Thread-1被暂停
Thread-2获取到锁并执行关键部分的代码,只有一个线程可以执行Python字节码。
在Thread-2执行完关键部分的代码之后,GIL可能会释放,然后操作系统决定将执行权交给Thread-1。

GIL被释放
线程切换到Thread-1
Thread-1继续执行其剩余的代码。

在这个例子中,操作系统的调度机制和全局解释器锁 (GIL) 相互作用,导致Thread-1和Thread-2之间的多次切换,并且只有一个线程可以执行Python字节码的关键部分。

太迷糊了,搞不懂

死锁

python中在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。

import threading
import time
from threading import Thread

a = threading.Lock()
b = threading.Lock()


class MyThread(Thread):
    def __init__(self):
        super().__init__()

    def func_1(self):
        a.acquire()
        print(f'线程{self.name},想执行a,{time.ctime()}')
        b.acquire()
        print(f'线程{self.name},想执行b,{time.ctime()}')
        a.release()
        b.release()

    def func_2(self):
        b.acquire()
        print(f'线程{self.name},想执行b,{time.ctime()}')
        time.sleep(0.01)
        a.acquire()
        print(f'线程{self.name},想执行a,{time.ctime()}')
        b.release()
        a.release()

    def run(self):
        self.func_1()
        self.func_2()


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

结果

线程Thread-1,想执行a,Mon Jul 24 08:55:24 2023
线程Thread-1,想执行b,Mon Jul 24 08:55:24 2023
线程Thread-1,想执行b,Mon Jul 24 08:55:24 2023
线程Thread-2,想执行a,Mon Jul 24 08:55:24 2023

在这种情况下就是在同一线程中多次请求同一资源时候出现的问题

递归锁

为了支持在同一线程中多次请求同一资源,python提供了"递归锁":threading.RLockRLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源

import threading
import time
from threading import Thread

lock = threading.RLock()


class MyThread(Thread):
    def __init__(self):
        super().__init__()

    def func_1(self):
        lock.acquire()
        print(f'线程{self.name},想执行a,{time.ctime()}')
        lock.acquire()
        print(f'线程{self.name},想执行b,{time.ctime()}')
        lock.release()
        lock.release()

    def func_2(self):
        lock.acquire()
        print(f'线程{self.name},想执行b,{time.ctime()}')
        time.sleep(0.01)
        lock.acquire()
        print(f'线程{self.name},想执行a,{time.ctime()}')
        lock.release()
        lock.release()

    def run(self):
        self.func_1()
        self.func_2()


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

上面我们用一把递归锁,就解决了多个同步锁导致的死锁问题。大家可以把RLock理解为大锁中还有小锁,只有等到内部所有的小锁,都没有了,其他的线程才能进入这个公共资源。

条件变量Condition
  1. 方法
  • acquire — 线程锁
  • release — 释放锁
    • 注意线程条件变量 Condition 中的所有相关函数使用必须在acquire / release 内部操作;
  • **wait( timeout ) **— 线程挂起(阻塞状态),直到收到一个 notify 通知或者超时才会被唤醒继续运行(超时参数默认不设置,可选填,类型是浮点数,单位是秒)。wait 必须在已获得 Lock 前提下才能调用,否则会触发 RuntimeError;
  • **notify(n=1) **— 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,缺省参数,默认是通知一个正等待通知的线程,最多则唤醒 n 个等待的线程。 notify 必须在已获得 Lock 前提下才能调用,否则会触发 RuntimeError ,notify 不会主动释放 Lock
  • notifyAll — 如果 wait 状态线程比较多,notifyAll 的作用就是通知所有线程;
  1. 原理

Python 条件变量 Condition 也需要关联互斥锁,同时 Condition 自身提供了 wait / notify / notifyAll 方法,用于阻塞 / 通知其他并行线程,可以访问共享资源了。

可以这么理解,Condition 提供了一种多线程通信机制,假如线程 1 需要数据,那么线程 1 就阻塞等待,这时线程 2 就去制造数据,线程 2 制造好数据后,通知线程 1 可以去取数据了,然后线程 1 去获取数据。

import threading
import time

money = 500
sign = '+'


def sub(name, num):
    con.acquire()
    global money, sign
    while money > 0:
        if sign == '-':
            sign = '+'
            money -= num
            print(f'{name}取了{num}--余额{money}')
            time.sleep(0.1)
            con.notify()
        else:
            con.wait()
    con.release()


def add(name, num):
    con.acquire()
    global money, sign

    while money < 1000 and money != 0:
        if sign == '+':
            sign = '-'
            money += num
            print(f'{name}往卡里存了{num}--余额{money}')
            time.sleep(0.1)
            con.notify()
        else:
            con.wait()
    con.release()


if __name__ == '__main__':
    con = threading.Condition()

    t1 = threading.Thread(target=add, args=('A', 100))
    t2 = threading.Thread(target=sub, args=('B', 200))
    t1.start()
    t2.start()

    t1.join()
    t2.join()
信号量Semaphore

**信号量用来控制线程并发数的 ** Semaphore 内部有一个计数器,acquire会使计数器-1,release会使计数器+1 等于0时会阻碍线程,直到计数器不为零

import random
import threading
import time

a = ['左勾拳','右勾拳','脚']
def fight():
    semaphore.acquire()
    global a
    print(f'{threading.currentThread().getName()}给了你一{random.choice(a)}')
    time.sleep(2)
    semaphore.release()


if __name__ == '__main__':
    semaphore = threading.Semaphore(2)

    for i in range(10):
        t = threading.Thread(target=fight)
        t.start()

结果:

Thread-1给了你一右勾拳
Thread-2给了你一脚

Thread-3给了你一右勾拳
Thread-4给了你一左勾拳

Thread-5给了你一脚Thread-6给了你一右勾拳

Thread-7给了你一左勾拳Thread-8给了你一左勾拳

Thread-9给了你一左勾拳Thread-10给了你一右勾拳


进程已结束,退出代码0

可以看到是两个两个的运行

Event对象

Event对象跟条件变量Condition差不多,存在一个标识符Flag,当标识符为False时,会阻塞线程运行;当标识符为True时,线程会执行。

方法:

event.isSet():返回event的状态值
event.wait():如果 event.isSet()==False,将阻塞线程触发event.wait()
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待执行
event.clear():恢复event的状态值为False
import threading


def get_cake():
    # 放面包
    print('放第一层面包')
    event.wait()
    print('放第二层面包')
    event.set()
    event.clear()
    event.wait()
    print('放第三层面包')




def cream():
    # 放奶油
    print('放第一层奶油')
    event.set()
    event.clear()
    event.wait()
    print('放第二层奶油')
    event.set()



if __name__ == '__main__':
    event = threading.Event()

    t1 = threading.Thread(target=get_cake)
    t2 = threading.Thread(target=cream)
    print('做蛋糕'.center(10,'-'))
    t1.start()
    t2.start()

    t1.join()
    t2.join()
    print('做完了'.center(10,'-'))

结果:

---做蛋糕----
放第一层面包
放第一层奶油
放第二层面包
放第二层奶油
放第三层面包
---做完了----
同步队列

队列中提供了一个先进先出的数据结构,实现生产者线程和消费者线程之间的消息传递

队列的常规用法

创建一个“队列”对象
import queue
q = queue.Queue(maxsize = 10)
queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。

将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,
get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。

Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。   class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。               class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。        class queue.PriorityQueue(maxsize)

此包中的常用方法(q = Queue.Queue()):
q.qsize() 返回队列的大小
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False)
非阻塞 q.put(item) 写入队列,timeout等待时间
q.put_nowait(item) 相当q.put(item, False)
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作

用法 可以生产者线程put到队列,消费者线程get获取

或者 put 后 用task_done 唤醒消费者队列 ,消费者 get后用join判断(每task_done一次 就从队列里删掉一个元素,这样在最后join的时候根据队列长度是否为零来判断队列是否结束,从而执行主线程)

线程池

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

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

此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。

基本使用
from concurrent.futures import ThreadPoolExecutor

def func(name):
    for i in range(10):
        print(name, i)

if __name__ == '__main__':
    with ThreadPoolExecutor(10) as t:
        # 100个任务,10个10个一起跑
        for i in range(100):
            t.submit(func,f"a{i}")
主要方法

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

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

# Exectuor
- submit(fn, *args, **kwargs):将 fn 函数提交给线程池。*args 代表传给 fn 函数的参数,*kwargs 代表以关键字参数的形式为 fn 函数传入参数。

- map(func, *iterables, timeout=None, chunksize=1):该函数类似于全局函数 map(func, *iterables),只是该函数将会启动多个线程,以异步方式立即对 iterables 执行 map 处理。

- shutdown(wait=True):关闭线程池。

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

# Future
- cancel():取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。

- cancelled():返回 Future 代表的线程任务是否被成功取消。

- running():如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。

- done():如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。

- result(timeout=None):获取该 Future 代表的线程任务最后返回的结果。如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,其中 timeout 参数指定最多阻塞多少秒。

- exception(timeout=None):获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。

- add_done_callback(fn):为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数。
from concurrent.futures import ThreadPoolExecutor
import time

def func(name,t):
    time.sleep(t)
    print("我是",name)
    return name

def fn(res):
    # 通过result()来获取返回值
    print(res.result())

if __name__ == '__main__':
    with ThreadPoolExecutor(3) as t:
        # t.submit(func,"a",2).add_done_callback(fn)## 任务完成后立即执行后面的
        # t.submit(func,"b",1).add_done_callback(fn)
        # t.submit(func,"c",3).add_done_callback(fn)
        # #   t.submit().add_done_callback() 返回即执行 callback函数
        # #   返回callback执行的顺序是不确定的。返回值的顺序是不确定的

        result = t.map(func,["a","b","c"],[2,1,3])#参数要是可迭代的  map:映射
        for r in result: # map返回值是一个生成器,返回的内容和任务分发的顺序是一致的
            print(r)

以上就是关于Python 线程的一些简要介绍和代码示例。希望对你有所帮助!如果你还有其他问题或需要进一步的解释,请随时提问。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值