python----线程、进程、协程的区别及多线程详解

一、线程、进程、协程区别

1. 进程是资源分配的独立单位,是应用程序的载体, 进程之间是相互独立(资源不共享),一个进程由多个线程构成

2.线程是资源分配的最小单位,线程共享进程的资源

3.协程是用户态执行的轻量级编程模型,由单一线程内部发出控制信号进行调度,协程允许不同入口点在不同的位置暂停或开始程序,协程本质就是一个小线程

多线程适合于 I/O 密集型任务,如网络请求、文件读写等,可以提高并发性和响应性。
多进程适用于 CPU 密集型任务,如大量计算、图像处理等,可以利用多核处理器加速运算。

二、创建线程

在Python中创建线程主要依靠内置的threading模块。线程类Thread的常用方法如下表:

序号方法含义
1start()创建一个Thread子线程实例并执行该实例的run()方法
2run()子线程需要执行的目标任务
3join()主进程阻塞等待子线程直到子线程结束才继续执行,可以设置等待超时时间timeout
4is_alive()判断子线程是否终止
5daemon()设置子线程是否随主进程退出而退出,默认是False

threading.current_thread():获取到当前线程。

获取线程后可以得到两个比较重要的属性:name和ident,分别是线程名称和id。

创建线程可以使用两种方法:使用函数或类创建。

1、函数创建

import threading
import time

def myThread(i):
    start = time.time()
    my_thread_name = threading.current_thread().name  # 当前线程的名字

    time.sleep(i)
    my_thread_id = threading.current_thread().ident  # 当前线程id
    print('当前线程为:{},线程ID:{},所在进程为:{}'.format(my_thread_name, my_thread_id, os.getpid()))
    print('%s线程运行时间结束,耗时%s...' % (my_thread_name, time.time() - start))

# 创建三个线程
def fun():
    t1 = time.time()
    thread = []
    for i in range(1, 4):
        t = threading.Thread(target=myThread,name='线程%s' % i, args=(i,))
        t.start()
        thread.append(t)
    for i in thread:
        i.join()  # 阻塞主线程

if __name__ == '__main__':
    fun()
threading.Thread(group=None,target=None,name='',args=(),kwargs={},daemon=False)
group:预留参数,不需要传递
target:目标代码(要执行的内容)
name:线程名称:字符串
args:是target的参数,可迭代对象
kwargs:是target的参数,是一个字典
daemon:设置线程为守护线程

2、类创建

class myThread(threading.Thread):
    def __init__(self,name=None):
        super().__init__()
        self.name = name

    def run(self):
        start = time.time()
        my_thread_name = threading.current_thread().name  # 当前线程的名字

        time.sleep(3)
        my_thread_id = threading.current_thread().ident  # 当前线程id
        print('当前线程为:{},线程ID:{},所在进程为:{}'.format(my_thread_name, my_thread_id, os.getpid()))
        print('%s线程运行时间结束,耗时%s...' % (my_thread_name, time.time() - start))

# 创建三个线程
def fun():
    t1 = time.time()
    thread = []
    for i in range(1, 4):
        t = myThread(name='线程%s' % i)
        t.start()
        thread.append(t)
    for i in thread:
        i.join()  # 阻塞主线程,直到子线程执行完毕再运行主线程

if __name__ == '__main__':
    fun()

三、线程锁

多线程一个很大的问题是数据不安全,因为线程之间的数据是共享的。多线程可以通过线程锁来进行数据同步,可用于保护共享资源同时被多个线程读写引起冲突导致错误

import threading
x = 0
# lock = threading.RLock()
def increment():
    global x
    name = threading.current_thread().name
    for _ in range(1000000):
        x += 1
    print(name, x)
threads = []
for _ in range(3):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()
for t in threads:
    t.join()
print("Final value of x:", x)

#Thread-2 1435674
#Thread-1 1423093
#Thread-3 1727936
#;Final value of x: 1727936

可以看出开启三个线程在0的基础上每次加1000000得到的结果应该是3000000.但不是这样的

1、Lock

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源设置一个状态:锁定和非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

import threading
x = 0
lock = threading.Lock()
def increment():
    global x
    name = threading.current_thread().name
    lock.acquire()
    try:
        for _ in range(1000000):
            x += 1
        print(name, x)
    finally:
        lock.release()
threads = []
for _ in range(3):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()

for t in threads:
    t.join()
print("Final value of x:", x)

#Thread-1 1000000
#Thread-2 2000000
#Thread-3 3000000
#Final value of x: 3000000

使用了锁之后,代码运行速度明显降低,这是因为线程由原来的并发执行变成了串行,不过数据安全性得到保证。

还可以使用with lock这种上下文格式,自动管理上锁和释放锁。

import threading
x = 0
lock = threading.Lock()
def increment():
    global x
    name = threading.current_thread().name
    with lock:
        for _ in range(1000000):
            x += 1
        print(name, x)
threads = []
for _ in range(3):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()
for t in threads:
    t.join()
print("Final value of x:", x)

#Thread-1 1000000
#Thread-2 2000000
#Thread-3 3000000
#Final value of x: 3000000
一般来说加锁以后还要有一些功能实现,在释放之前还有可能抛异常,一旦抛出异常,锁是无法释放,但是当前线程可能因为这个异常被终止了,这就产生了死锁。

死锁解决办法:
1、使用 try..except..finally 语句处理异常、保证锁的释放

2、with 语句上下文管理,锁对象支持上下文管理。只要实现了__enter__和__exit__魔术方法的对象都支持上下文管理。

另一种是
锁的应用场景:

独占锁: 锁适用于访问和修改同一个共享资源的时候,即读写同一个资源的时候。

共享锁: 如果共享资源是不可变的值时,所有线程每一次读取它都是同一样的值,这样的情况就不需要锁。

使用锁的注意事项:

少用锁,必要时用锁。使用了锁,多线程访问被锁的资源时,就变成了串行,要么排队执行,要么争抢执行。
加锁时间越短越好,不需要就立即释放锁。
一定要避免死锁。
不使用锁时,有了效率,但是结果是错的。

使用了锁,变成了串行,效率地下,但是结果是对的。

2、死锁

2.1 加锁之后处理业务逻辑,在释放锁之前抛出异常,这时的锁没有正常释放,当前的线程因为异常终止了,就会产生死锁。
解决方案:
1、使用 try..except..finally 语句处理异常、保证锁的释放

2、with 语句上下文管理,锁对象支持上下文管理。只要实现了__enter__和__exit__魔术方法的对象都支持上下文管理。
2.2 开启两个或两个以上的线程,不同的线程得到了不同的锁,都在等待对方释放锁,但是都在阻塞,所以产生了死锁
解决方案:
1、不同线程中获得锁的顺序一致,不能乱

2、使用递归锁:RLock。
2.3 在同一线程里,多次取获得锁,第一次获取锁后,还未释放,再次获得锁
解决方案:
1、使用递归锁:RLock。

3、RLock

递归锁也被称为“锁中锁”,指一个线程可以多次申请同一把锁,但是不会造成死锁,这就可以用来解决上面的死锁问题。

import threading
x = 0
lock = threading.RLock()
def increment():
    global x
    name = threading.current_thread().name
    lock.acquire()
    lock.acquire()
    try:
        for _ in range(1000000):
            x += 1
        print(name, x)
    finally:
        lock.release()
        lock.release()
threads = []
for _ in range(3):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()
for t in threads:
    t.join()
print("Final value of x:", x)

#Thread-1 1000000
#Thread-2 2000000
#Thread-3 3000000
#Final value of x: 3000000

RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

四、线程通信

1、condition

Condition可以认为是一把比Lock和RLOK更加高级的锁,其在内部维护一个琐对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。

运行原理:

可以认为Condition对象维护了一个锁(Lock/RLock)和一个waiting池。线程通过acquire获得Condition对象,
当调用wait方法时,线程会释放Condition内部的锁并进入blocked状态,同时在waiting池中记录这个线程。
当调用notify方法时,Condition对象会从waiting池中挑选一个线程,通知其调用acquire方法尝试取到锁。

Condition对象的构造函数可以接受一个Lock/RLock对象作为参数,如果没有指定,则Condition对象会在内部自行创建一个RLock。

除了notify方法外,Condition对象还提供了notifyAll方法,可以通知waiting池中的所有线程尝试acquire内部锁。
由于上述机制,处于waiting状态的线程只能通过notify方法唤醒,所以notifyAll的作用在于防止有的线程永远处于沉默状态。
import threading
import time
from queue import Queue

class Producer(threading.Thread):
    # 生产者函数
    def run(self):
        global count
        while True:
            if con.acquire():
                # 当count 小于等于1000 的时候进行生产
                if count > 1000:
                    con.wait()
                else:
                    count = count + 100
                    msg = self.name + ' produce 100, count=' + str(count)
                    print(msg)
                    # 完成生成后唤醒waiting状态的线程,
                    # 从waiting池中挑选一个线程,通知其调用acquire方法尝试取到锁
                    con.notify()
                con.release()
                time.sleep(1)


class Consumer(threading.Thread):
    # 消费者函数
    def run(self):
        global count
        while True:
            # 当count 大于等于100的时候进行消费
            if con.acquire():
                if count < 100:
                    con.wait()
                else:
                    count = count - 5
                    msg = self.name + ' consume 5, count=' + str(count)
                    print(msg)
                    con.notify()
                    # 完成生成后唤醒waiting状态的线程,
                    # 从waiting池中挑选一个线程,通知其调用acquire方法尝试取到锁
                con.release()
                time.sleep(1)

count = 0
con = threading.Condition()

def test():
    for i in range(2):
        p = Producer()
        p.start()
    for i in range(5):
        c = Consumer()
        c.start()
if __name__ == '__main__':
    test()

#Thread-1 produce 100, count=100
#Thread-2 produce 100, count=200
#Thread-3 consume 5, count=195
#Thread-4 consume 5, count=190
#Thread-5 consume 5, count=185
#Thread-6 consume 5, count=180
#Thread-7 consume 5, count=175
#Thread-2 produce 100, count=275
#Thread-3 consume 5, count=270

2、semaphore

semaphore是python中的一个内置的计数器,内部使用了Condition对象,多线程同时运行,能提高程序的运行效率,但是并非线程越多越好,而 semaphore 信号量可以通过内置计数器来控制同时运行线程的数量,启动线程(消耗信号量)内置计数器会自动减一,线程结束(释放信号量)内置计数器会自动加一;内置计数器为零,启动线程会阻塞,直到有本线程结束或者其他线程结束为止;
创建多个线程,同一时间运行三个线程

import threading
# 导入时间模块
import time

# 添加一个计数器,最大并发线程数量5(最多同时运行5个线程)
semaphore = threading.Semaphore(2)

def foo():
    semaphore.acquire()    #计数器获得锁
    time.sleep(2)   #程序休眠2秒
    print("当前时间:",time.ctime()) # 打印当前系统时间
    semaphore.release()    #计数器释放锁


if __name__ == "__main__":
    thread_list= list()
    for i in range(6):
        t=threading.Thread(target=foo,args=()) #创建线程
        thread_list.append(t)
        t.start()  #启动线程
    for t in thread_list:
        t.join()
    print("程序结束!")

#当前时间: Tue Jul 30 11:18:38 2024
#当前时间: Tue Jul 30 11:18:38 2024
#当前时间: Tue Jul 30 11:18:40 2024
#当前时间: Tue Jul 30 11:18:40 2024
#当前时间: Tue Jul 30 11:18:42 2024
#当前时间: Tue Jul 30 11:18:42 2024
#程序结束!

因为Semaphore使用了Condition,线程之间仍然有锁保证线程数据安全

3、Event

事件。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时候就可以用threading为我们提供的Event对象。

事件处理的机制:全局定义了一个内置标志Flag,如果Flag值为 False,那么当程序执行 event.wait方法时就会阻塞,如果Flag值为True,那么event.wait 方法时便不再阻塞。

Event其实就是一个简化版的 Condition。Event没有锁,无法使线程进入同步阻塞状态。

方法:
      set — 全局内置标志 Flag,将标志 Flag 设置为 True,通知在等待状态 ( wait ) 的线程恢复运行;
      isSet — 获取标志 Flag 当前状态,返回 True 或者 False ;
      wait — 一旦调用,线程将会处于阻塞状态,直到等待其他线程调用 set 函数恢复运行;
      clear — 将标志设置为False
from threading import Thread, Event
import time, random


def now():
    return str(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))


def traffic_light(e):  # 红绿灯
    print(now() + ' \033[31m红灯亮\033[0m')  # Flag 默认是False
    while True:
        if e.is_set():  # 如果是绿灯
            time.sleep(2)  # 2秒后
            print(now() + ' \033[31m红灯亮\033[0m')  # 转为红灯
            e.clear()  # 设置为False

        else:  # 如果是红灯
            time.sleep(2)  # 2秒后
            print(now() + ' \033[32m绿灯亮\033[0m')  # 转为绿灯
            e.set()  # 设置为True

def people(e, i):
    if not e.is_set():
        print(now() + ' people %s 在等待' % i)
        e.wait()
    print(now() +' people %s 通过了' % i)


if __name__ == '__main__':
    e = Event()  # 默认为 False,红灯亮
    p = Thread(target=traffic_light, args=(e,))  # 红绿灯进程
    p.daemon = True
    p.start()
    process_list = []
    for i in range(1, 7):  # 6人过马路
        time.sleep(random.randrange(0, 4, 2))
        p = Thread(target=people, args=(e, i))
        p.start()
        process_list.append(p)

    for p in process_list:
        p.join()

#2024-07-30 14:24:01 红灯亮
#2024-07-30 14:24:01 people 1 在等待
#2024-07-30 14:24:03 绿灯亮
#2024-07-30 14:24:03 people 1 通过了
#2024-07-30 14:24:03 people 2 通过了
#2024-07-30 14:24:03 people 3 通过了
#2024-07-30 14:24:03 people 4 通过了
#2024-07-30 14:24:03 people 5 通过了
#2024-07-30 14:24:05 红灯亮
#2024-07-30 14:24:05 people 6 在等待
#2024-07-30 14:24:07 绿灯亮
#2024-07-30 14:24:07 people 6 通过了

4、queue

queue模块是python内置的标准模块,实现了三种类型的队列,分别由三个类表示Queue先进先出队列FIFO,LifoQueue先进后出队列LIFO,PriorityQueue优先级队列构造器

4.1 queue.Queue(maxsize=0)

先进先出队列 FIFO
maxsize 是个整数,用于设置可以放入队列中的项目数的上限。当达到这个大小的时候,插入操作将阻塞至队列中的项目被消费掉。如果 maxsize 小于等于零,队列尺寸为无限大。

from queue import Queue,LifoQueue

# FIFO
queue_obj = Queue()  # 创建一个队列对象
for i in range(4):
    queue_obj.put(i)
while not queue_obj.empty():
    print(queue_obj.get())

# 输出顺序
0
1
2
3
4.2 queue.LifoQueue(maxsize=0)

后进先出队列 LIFO

queue_obj = LifoQueue()  # 创建一个队列对象
for i in range(4):
    queue_obj.put(i)
while not queue_obj.empty():
    print(queue_obj.get())

# 输出顺序
3
2
1
0
4.3 queue.PriorityQueue(maxsize=0)

优先级队列构造器,按照级别顺序取出元素,级别最低的最先取出
队列中的元素一般采取元组的形式进行存储(priority_number, data)

  • 优先级不同数据部分可以比较大小
PriorityQueue_obj = PriorityQueue()
PriorityQueue_obj.put((5,12))
PriorityQueue_obj.put((2,11))
PriorityQueue_obj.put((3,15))
while not PriorityQueue_obj.empty():
    print(PriorityQueue_obj.get())
# 输出顺序
(2, 11)
(3, 15)
(5, 12)  # 级别低的先取出
  • 优先级一样,数据部分可以比较大小
PriorityQueue_obj = PriorityQueue()
PriorityQueue_obj.put((2,12))
PriorityQueue_obj.put((2,11))
PriorityQueue_obj.put((3,15))
while not PriorityQueue_obj.empty():
    print(PriorityQueue_obj.get())
    
PriorityQueue_obj = PriorityQueue()
PriorityQueue_obj.put((2,[12,14]))
PriorityQueue_obj.put((2,[3,15]))
PriorityQueue_obj.put((3,'23'))
while not PriorityQueue_obj.empty():
    print(PriorityQueue_obj.get())

# 输出顺序
(2, 11)
(2, 12)
(3, 15)
(2, [3, 15])
(2, [12, 14])
(3, '23')
# 优先级一样时,比较数据部分的大小,字符串也能比较
# 字典不能直接比较,会报错TypeError: '<' not supported between instances of 'dict' and 'dict'
# 想要实现字典这类的比较,在类中自定义或重写 def __lt__(self, other):魔法方法,
# 其作用是实现同类对象进行“比较”的方法,在类进行比较的时候可以自动调用
class dic:
    def __init__(self,level,data):
        self.level = level
        self.data = data
    def __lt__(self, other):
        if self.level == other.level:
            return len(self.data)<len(other.data)
        return self.level < other.level

priorityQueue_obj = PriorityQueue()
priorityQueue_obj.put(dic(5, {"e":4, 2:5}))
priorityQueue_obj.put(dic(4, {"y":4}))
priorityQueue_obj.put(dic(5, {"l":2}))
while not priorityQueue_obj.empty():
    print(priorityQueue_obj.get().data)
    
# 输出顺序
{'y': 4}
{'l': 2}
{'e': 4, 2: 5}
  • Queue.empty()
    如果队列为空,返回 True ,否则返回 False 。
  • Queue.full()
    如果队列是满的返回 True ,否则返回 False 。
  • Queue.put(item, block=True, timeout=None)
    将 item 放入队列。如果可选参数 block 是 true 并且 timeout 是 None (默认),则在必要时阻塞至有空闲插槽可用。如果 timeout 是个正数,将最多阻塞 timeout 秒,如果在这段时间没有可用的空闲插槽,将引发 Full 异常。反之 (block 是 false),如果空闲插槽立即可用,则把 item 放入队列,否则引发 Full 异常 ( 在这种情况下,timeout 将被忽略)。
  • Queue.put_nowait(item)
    相当于 put(item, block=False)。
  • Queue.get(block=True, timeout=None)
    从队列中移除并返回一个项目。如果可选参数 block 是 true 并且 timeout 是 None (默认值),则在必要时阻塞至项目可得到。如果 timeout 是个正数,将最多阻塞 timeout 秒,如果在这段时间内项目不能得到,将引发 Empty 异常。反之 (block 是 false) , 如果一个项目立即可得到,则返回一个项目,否则引发 Empty 异常 (这种情况下,timeout 将被忽略)。
  • Queue.get_nowait()
    相当于 get(block=False) 。
  • Queue.task_done()
    在完成一项工作以后,task_done()告诉队列,该任务已处理完成
  • Queue.join()
    阻塞至队列中所有的元素都被接收和处理完毕。
    队列添加新工作时,未完成任务的计数就会增一,当调用task_done()函数后,就代表执行完一个工作,未完成任务的计数就会减一,当计数为0时 join() 阻塞被解除。
4.4 多线程的Queue是在queue模块中
def getter(queue):
    while True:
        try:
            time.sleep(random.random())
            value = queue.get(True)
            print('getter value:%f' % value)
        except Exception as e:
            raise Exception(e)


def putter(queue):
    for i in range(1, 11):
        time.sleep(1)
        queue.put(i)
        print('putter value:%f' % i)


if __name__ == '__main__':
    queue = Queue()
    putter = threading.Thread(target=putter(queue))
    getter = threading.Thread(target=getter(queue))
    getter.start()
    putter.start()

五、线程池

在我们上面执行多个任务时,使用的线程方案都是“即时创建, 即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。所以就有了线程池的诞生,

由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。
线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。

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

1、线程池的使用

线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。
如果使用线程池/进程池来管理并发编程,那么只要将相应的 task 函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定。

2、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 来代表。

3、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 函数。

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

def test(value1, value2=None):
    print("%s threading is printed %s, %s"%(threading.current_thread().name, value1, value2))
    time.sleep(2)
    return 'finished'

def test_result(future):
    print(future.result())

if __name__ == "__main__":
    import numpy as np
    from concurrent.futures import ThreadPoolExecutor
    threadPool = ThreadPoolExecutor(max_workers=4, thread_name_prefix="test_")
    for i in range(0,10):
        future = threadPool.submit(test, i,i+1)
        future.add_done_callback(test_result)  # 添加回调函数,线程任务完成时会自动触发
        
        # if future.done():
        #     future.result()
        
        # print(future.result()) # 直接调用会阻塞主线程,知道线程任务完成才会解除

    threadPool.shutdown(wait=True)
    print('main finished')

由于线程池实现了上下文管理协议(Context Manage Protocol),因此,程序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池。如以上代码
with ThreadPoolExecutor(max_workers=4, thread_name_prefix=“test_”) as threadPool:

4、map

此外,Exectuor 还提供了一个 map(func, *iterables, timeout=None, chunksize=1) 方法,该方法的功能类似于全局函数 map(),区别在于线程池的 map() 方法会为 iterables 的每个元素启动一个线程,以并发方式来执行 func 函数。这种方式相当于启动 len(iterables) 个线程,井收集每个线程的执行结果。

from concurrent.futures import ThreadPoolExecutor, as_completed
import time
# 需要传入多参数
def get_(time1,time2):
    time.sleep(time1)
    print(time1)
    print(time2)
    return time1

executor = ThreadPoolExecutor(max_workers=2)
for data in executor.map(get_, [1,2,3],[4,5,6]):
    print(f"in main: get page {data}s success")
    # map可以让执行多次,即一个[]代表一个参数,一个参数赋予不同的值即增加[]的长度如从[1]到[1,2,3]

#1
#4
#in main: get page 1s success
#2
#5
#in main: get page 2s success
#3
#6
#in main: get page 3s success

使用 map() 方法来启动线程,并收集线程的执行结果,不仅具有代码简单的优点,而且虽然程序会以并发方式来执行函数,但最后收集的函数的执行结果,依然与传入参数的结果保持一致。

5、as_completed

收集线程任务的返回结果还有concurrent.futures下的as_completed方法,是一个生成器,在没有任务完成的时候,会阻塞,当有任务完成的时候,就会yield这个任务,就能执行for循环下面的语句,然后继续阻塞住,等待下一个任务完成,直到所有的任务结束。

from concurrent.futures import ThreadPoolExecutor, as_completed
import time

def get_(times):
    time.sleep(times)
    print(f"get page {times}s finished")
    return times

executor = ThreadPoolExecutor(max_workers=2)
times = [4262]
all_task = [executor.submit(get_,time)for time in times]
# for data in executor.map(get_html, [1,2,3],[4,5,6]):
#     print(f"in main: get page {data}s success")
for task in as_completed(all_task):
    data = task.result()
    print(f"in main: get page {data}s success")
executor.shutdown(wait=True)

#get page 2s finished
#in main: get page 2s success
#get page 4s finished
#in main: get page 4s success
#get page 2s finished
#in main: get page 2s success
#get page 6s finished
#in main: get page 6s success

5、wait

 wait(fs, timeout=None, return_when=ALL_COMPLETED)
  • fs: 表示需要执行的序列
  • timeout: 等待的最大时间,如果超过这个时间即使线程未执行完成也将返回
  • return_when:表示wait返回结果的条件,默认为 ALL_COMPLETED 全部执行完成再返回
from concurrent.futures import ThreadPoolExecutor, as_completed,wait,FIRST_COMPLETED,ALL_COMPLETED
import time


def get_(times):
    time.sleep(times)
    print(f"get page {times}s finished")
    return times

with ThreadPoolExecutor(max_workers=2) as executor:
    times = [4,2,6,2]
    all_task = [executor.submit(get_,time)for time in times]
    wait(all_task,return_when=FIRST_COMPLETED)
    # FIRST_COMPLETED 当完成第一个任务的时候,就停止等待,继续主线程任务
    print('finished')

#get page 2s finished
#finished
#get page 4s finished
#get page 2s finished
#get page 6s finished

六、定时器Timer

  • Timer:隔一定时间调用一个函数,如果想实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer。Timer是Thread的一个派生类.
  • 它的实例是能够延迟执行目标函数的线程,在真正执行目标函数之前,都可以cancel取消它。
from threading import Timer
import time

def add(x, y):
    print(x + y)

while True:
    t = Timer(2,add,args=[4,5,])
    t.start()

    time.sleep(2)

Timer继承子Thread类,是Thread的子类,也是线程类,具有线程的能力和特征。这个类用来定义多久执行一个函数。
Timer用的是Thread模块,每启动一个定时器,启动一个线程

参考:https://blog.csdn.net/qq_43745578/article/details/128754615

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值