python多任务—线程

线程是CPU调度的单位,在python中,由于GIL的存在,所以多线程也不是真正的多线程,它是通过竞争GIL锁,得到使用CPU的权限。

多线程,threading模块的使用

import threading
import os, time

def thread_run(num):
    print("The thread {} is running!".format(num))
    time.sleep(5)  # 这里睡了5秒,主要是为了比较下面两种方式的运行效果,使用多线程几乎是同时出现的(一闪而过),普通方法是5秒出现一个
    
if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=thread_run, args=(i, ))
        t.start()
#         thread_run(i)
The thread 0 is running!The thread 1 is running!

The thread 2 is running!
The thread 3 is running!
The thread 4 is running!

说明:

  • 比较上面两种执行方式可以发现:使用多线程并发的操作,花费时间要短很多
  • 在调用start()时,才会真正的创建线程,并且开始执行

查看当前运行的线程数量

import threading
import time


def thread_sing():
    for i in range(5):
        print("The thread is singing----{}".format(i))
        time.sleep(1)

        
def thread_dance():
    for i in range(5):
        print("The thread is dancing----{}".format(i))
        time.sleep(1)
        
        
if __name__ == "__main__":
    print("Start at {}".format(time.ctime()))
    
    t1 = threading.Thread(target=thread_sing)
    t2 = threading.Thread(target=thread_dance)
    
    t1.start()
    t2.start()
    
    while True:
        length = len(threading.enumerate())
        print("Currently, the number of running thread is {}".format(length))
        if length <= 1:
            break
        time.sleep(0.5)

运行结果如下:从运行结果上来看,这两个子线程是并行的,但只是从肉眼观察上觉得是。(图片中打错了两个字)

线程的封装

通过threading模块可以完成多任务程序开发,为了让每个线程的封装更完美,所以使用threading模块时,往往会定义一个子类,只要继承threading.Thread就可以了,然后重写run方法

import threading
import time

class My_thread(threading.Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name
        
    def run(self):
        print("The thread {} is running...".format(self.name))
        time.sleep(0.5)

if __name__ == "__main__":
    for i in range(5):
        t = My_thread(i)
        t.start()       
The thread 0 is running...The thread 1 is running...

The thread 2 is running...The thread 3 is running...
The thread 4 is running...

python 的threading。Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中重写这个方法。对自己定义的线程类实例化后,通过父类Thread的start方法,可以启动该线程,交给python虚拟机进行调度,当线程获得执行的机会后,就会调用run方法执行任务。

线程池的使用

使用标准库threading和multiprocessing模块可以实现多线程和多进程的任务,但是当项目达到一定的规模时,频繁的创建或销毁进程或线程,是非常耗资源的,这时候我们就要使用线程池或进程池,以空间换时间。

python标准库为我们提供了一个concurrent.futures的模块,它包括:ThreadPoolExecutor和ProcessPoolExecutor两个类,实现对threading和multiprocessing的进一步抽象,对编写线程池和进程池提供了直接的支持。

ThreadPoolExecutor和ProcessPoolExecutor创建线程池和进程池的类,我们把相应的任务(tasks)直接放到线程池或进程池中,不需要维护Queue来操行死锁的问题,线程池和进程池会自动帮我们调度。

future 可以理解为未来完成的操作,这是异步编程的基础,传统的编程模式下,如:queue.get的时候,等待结果返回之前会堵塞,而future的引入则会帮我们在等待的这段时间可以完成其他操作。

submit方法的使用: submit(fn, *args, * *kwargs):

安排可调用对象 fn 以 fn(*args, * *kwargs) 的形式执行,并返回 Future 对象来表示它的执行。

from concurrent.futures import ThreadPoolExecutor
import time
import requests


def thread_run(url):
    try:
        re = requests.get(url=url, timeout=30)
        re.raise_for_status()
        re.encoding = re.apparent_encoding
        return re.text
    except:
        return


if __name__ == "__main__":
    # 创建线程池实例对象
    executor = ThreadPoolExecutor(max_workers=2)
    
    # submit函数用来提交线程需要执行的任务到线程池中,并返回该任务的句柄(类似与文件,画图)(返回一个future实例),
    # 注意,submit()函数立即返回,不堵塞
    future1 = executor.submit(thread_run, "https://blog.csdn.net/weixin_41599977/article/details/92404112")
    future2 = executor.submit(thread_run, "https://blog.csdn.net/weixin_41599977/article/details/91048654")
    
    # submit函数返回的任务句柄可以使用done()方法判断该任务是否结束
    print(future1.done())
    
    # cancel函数可以取消提交的任务,但是如果任务已经在线程池中运行了,就取消不了了
    print(future2.cancel())

    time.sleep(4)
    print(future1.done())
    
    # result方法可以获取任务的返回值,这个方法是堵塞的(同步执行)
    print("the page 1 is {} bytes".format(len(future1.result())))
False
False
True
the page 1 is 197322 bytes

shutdown方法的使用: shutdown(wait=True):告诉执行器 executor 在当前所有等待的 future 对象运行完毕后,应该释放执行器用到的所有资源。

在 shutdown 之后再调用 Executor.submit() 和 Executor.map() 会报运行时错误 RuntimeError。

  • 如果 wait 为 True,那么这个方法会在所有等待的 future 都执行完毕,并且属于执行器 executor 的资源都释放完之后才会返回。
  • 如果 wait 为 False,本方法会立即返回。属于执行器的资源会在所有等待的 future 执行完毕之后释放。

不管 wait 取值如何,整个 Python 程序在等待的 future 执行完毕之前不会退出。

你可以通过 with 语句来避免显式调用本方法。with 语句会用 wait=True 的默认参数调用 Executor.shutdown() 方法。

from concurrent.futures import ThreadPoolExecutor
import time
import requests


def thread_run(url):
    try:
        print("thread_run is spiderring {} \n".format(url))
        re = requests.get(url=url, timeout=30)
        re.raise_for_status()
        re.encoding = re.apparent_encoding
        return re.text
    except:
        return
    
def main_thread():
    for i in range(3):
        print("I am the main_thread task, I am running -- {}".format(i))


if __name__ == "__main__":
#     executor = ThreadPoolExecutor(max_workers=2)
    
#     future1 = executor.submit(thread_run, "https://blog.csdn.net/weixin_41599977/article/details/92404112")
#     future2 = executor.submit(thread_run, "https://blog.csdn.net/weixin_41599977/article/details/91048654")
    
#     executor.shutdown()  # 与进程池pool中的close和join实现的功能一样,关闭pool主进(线)程等待子进(线)程结束后继续往下执行
    
#     main_thread()  # 主线程等到线程池里的子线程运行结束才能执行这里

################################################################################################################################

#     上面方法的一种等价形式
    urls = [
        "https://blog.csdn.net/weixin_41599977/article/details/92404112",
        "https://blog.csdn.net/weixin_41599977/article/details/91048654"
    ]
    with ThreadPoolExecutor(max_workers=2) as executor:  # 类似于打开文件,省去了executor.shutdown()这一步,建议这样使用
        for url in urls:
            executor.submit(thread_run, url)
   
    main_thread()
thread_run is spiderring https://blog.csdn.net/weixin_41599977/article/details/92404112 

thread_run is spiderring https://blog.csdn.net/weixin_41599977/article/details/91048654 

I am the main_thread task, I am running -- 0
I am the main_thread task, I am running -- 1
I am the main_thread task, I am running -- 2

as_completed方法的使用concurrent.futures.as_completed(fs,timeout = None ):

返回由fs给出的Future实例的迭代器,yield future实例(当它们完成或取消),当调用__next__()不能获得结果(在timeout时间后)时,会抛出异常concurrent.futures.TimeoutError

注意:是无序的,谁先完成就先yield谁,从下面运行的结果可以看出。

from concurrent.futures import ThreadPoolExecutor, as_completed
import requests


def thread_run(url):
    try:
        re = requests.get(url=url, timeout=30)
        re.raise_for_status()
        re.encoding = re.apparent_encoding
        return re.text
    except:
        return


if __name__ == "__main__":
    executor = ThreadPoolExecutor(max_workers=10)
    urls = [
        "https://blog.csdn.net/weixin_41599977/article/details/92404112",
        "https://blog.csdn.net/weixin_41599977/article/details/91048654",
        "https://blog.csdn.net/weixin_41599977/article/details/90300773"
    ]
    futures_2_url = {executor.submit(thread_run, url): url for url in urls}  # 字典表达式
    
    # as_complete方法是一个生成器,在没有任务完成时,会堵塞,当有某个任务完成的时候,
    # 就会yield这个任务,所以就能执行for循环下面的语句,然后继续堵塞
    for future in as_completed(futures_2_url):
        data = future.result()
        print("In the main thread: get page {} success, {} bytes.".format(futures_2_url[future], len(data)))

In the main thread: get page https://blog.csdn.net/weixin_41599977/article/details/90300773 success, 153822 bytes.
In the main thread: get page https://blog.csdn.net/weixin_41599977/article/details/92404112 success, 205110 bytes.
In the main thread: get page https://blog.csdn.net/weixin_41599977/article/details/91048654 success, 162118 bytes.

map方法的使用: map(func,* iterables,timeout = None,chunksize = 1 ),返回一个迭代器。与内置函数map(func,* iterables)有点相似,但是有两点不同:

  • 1、立即获取iterables,而不是惰性获取
  • 2、异步执行func,并且支持多次并发调用

从调用 Executor.map() 开始的 timeout 秒之后,如果在迭代器上调用了 __next__() 并且无可用结果的话,迭代器会抛出 concurrent.futures.TimeoutError 异常。timeout 秒数可以是浮点数或者整数,如果设置为 None 或者不指定,则不限制等待时间。

如果 func 调用抛出了异常,那么该异常会在从迭代器获取值的时候抛出。

当使用 ProcessPoolExecutor 的时候,这个方法会把 iterables 划分成多个块,作为独立的任务提交到进程池。这些块的近似大小可以通过给 chunksize 指定一个正整数。对于很长的 iterables,使用较大的 chunksize 而不是采用默认值 1,可以显著提高性能。对于 ThreadPoolExecutor,chunksize 不起作用。

注意: 不管并发任务的执行次序如何,map 总是基于输入顺序来返回值。map 返回的迭代器,在主程序迭代的时候,会等待每一项的响应。

from concurrent.futures import ThreadPoolExecutor
import requests


def thread_run(url):
    try:
        re = requests.get(url=url, timeout=30)
        re.raise_for_status()
        re.encoding = re.apparent_encoding
        return re.text
    except:
        return


if __name__ == "__main__":
    executor = ThreadPoolExecutor(max_workers=2)
    urls = [
        "https://blog.csdn.net/weixin_41599977/article/details/92404112",
        "https://blog.csdn.net/weixin_41599977/article/details/91048654",
        "https://blog.csdn.net/weixin_41599977/article/details/90300773"
    ]
    # 使用map方法,无需提前使用submit方法,map方法与标准库里额map含义相似,
    # 都是将序列中的每个元素都执行同一个函数。下面的代码就是对urls中的每个元
    # 素都执行thread_run函数,并分配到线程池中。
    for url, data in zip(urls, executor.map(thread_run, urls)):
        print("In the main thread: get page {} success, {} bytes".format(url, len(data)))

In the main thread: get page https://blog.csdn.net/weixin_41599977/article/details/92404112 success, 195849 bytes
In the main thread: get page https://blog.csdn.net/weixin_41599977/article/details/91048654 success, 171295 bytes
In the main thread: get page https://blog.csdn.net/weixin_41599977/article/details/90300773 success, 155611 bytes

wait方法的使用: concurrent.futures.wait(fs, timeout=None, return_when=ALL_COMPLETED):

等待 Future 实例完成,这些实例可能由多个不同的执行器实例创建,通过 fs 指定这些 Future 实例

返回具名元组,该元组有两个元素,每个元素都是一个集合。第一个元素名叫 done,该集合包括已完成的 futures;第二个元素名叫 not_done,该集合包括未完成的 futures。

  • timeout 用来控制返回之前等待的最大秒数,可以是整数或者浮点数。如果不指定或为 None,不限制等待时间。
  • return_when 指明函数何时应该返回。它必须是下列常量之一:
    • FIRST_COMPLETED:函数在任意一个 future 完成或者被取消时返回。
    • FIRST_EXCEPTION:函数在任意一个 future 因为异常而结束时返回。如果没有 future 抛出异常,它等价于 ALL_COMPLETED。
    • ALL_COMPLETED:当所有 future 完成或者被取消时函数才会返回。
from concurrent.futures import ThreadPoolExecutor, wait
import requests


def thread_run(url):
    try:
        re = requests.get(url=url, timeout=30)
        re.raise_for_status()
        re.encoding = re.apparent_encoding
        return re.text
    except:
        return


if __name__ == "__main__":
    with ThreadPoolExecutor(max_workers=2) as executor:
        urls = [
            "https://blog.csdn.net/weixin_41599977/article/details/92404112",
            "https://blog.csdn.net/weixin_41599977/article/details/91048654",
            "https://blog.csdn.net/weixin_41599977/article/details/90300773"
        ]
        futures = []
        for url in urls:
            futures.append(executor.submit(thread_run, url))
        # wait方法会返回一个tuple,元组中包含两个set,一个是completed,另一个是uncompleted
        # 使用wait方法的一个优势就是获得更大的自由度,它接收三个参数FIRST_COMPLETED, FIRST_EXCEPTION 和ALL_COMPLETE,
        # 默认设置为ALL_COMPLETED。
        # print(wait(tasks))  # 程序会阻塞在这里,直到线程池里的子线程全部完成任务
        print(wait(futures, return_when="FIRST_COMPLETED").done)  # 主线程并不会等待线程池里面的任务执行完,而是继续往下执行
        print("The main thread is running!")

{<Future at 0x1aaa7717a58 state=finished returned str>}
The main thread is running!

多线程共享全局变量

(同一进程里的)线程之间可以共享全局变量,但是随意更改可能会造成全局变量的混乱。

比如:假设两个线程,thread1和thread2,对全局变量global_num进行加1操作,thread1和thread2分别对global_num操作100次,那么global_num的最终结果应该是200,但是,由于是多线程同时操作,会出现以下情况:

  • 1、在global_num = 0时,thread1取得global_num。此时系统把thread1调度为“sleeping”状态;把thread2转换为“running”状态,thread2也获得global_num(此时global_num还是为0,thread1还没有对它进行加1操作);
  • 2、thread2对得到的值进行加1操作,使得global_num = 1
  • 3、然后系统又把thread2调度为“sleeping”,把thread1转为“running”。线程thread1又把它之前得到的0加1后赋值给global_num。
  • 4、这样导致虽然thread1和thread2都对global_num加1,但结果仍然是global_num=1
from concurrent.futures import ThreadPoolExecutor


def thread_run_1(num):
    global global_num
    for i in range(num):
        global_num += 1
    print("In thread 1, the global_num is {}".format(global_num))

def thread_run_2(num):
    global global_num
    for i in range(num):
        global_num += 1
    print("In thread 2, the global_num is {}".format(global_num))
    
if __name__ == "__main__":
    # 定义全变共享变量
    global_num = 0
    
    with ThreadPoolExecutor() as executor:
        for _ in range(5):
            executor.submit(thread_run_1, 100000)
            executor.submit(thread_run_2, 100000)  # 理论上10个线程对全局变量进行加1操作,最后的结果时1000000,但是结果不对(多运行几次观察)
    print("Finally, the global_num is {}".format(global_num))

In thread 1, the global_num is 100000
In thread 2, the global_num is 200000
In thread 1, the global_num is 300000In thread 2, the global_num is 329985In thread 1, the global_num is 429985


In thread 2, the global_num is 529985
In thread 1, the global_num is 629985
In thread 2, the global_num is 729985
In thread 1, the global_num is 829985
In thread 2, the global_num is 929985
Finally, the global_num is 929985

线程池里操作同一个全局变量,会出现资源竞争问题,从而导致数据结果会不正确

解决上述问题的办法:同步运行。

同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为,他是按照先后次序进行运行,比如:你先说,说完了我再说。“同”字的含义是:协同,协助、互相配合。再如:进程或线程同步,可以理解为进程或线程A、B一块配合,A执行到一定程度后需要依靠B的某个结果,于是停下来等待,并示意B运行,B运行完后将结果给A,A再继续往下运行。

同步和异步的简明理解:

  • 同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。
  • 异步是指:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。

互斥锁 :为资源引入一个状态:锁定和非锁定,这是多线程同步机制的一个最简单的方式

当某个线程要更改共享数据时,先将其“锁定”,使得其他线程不能修改,直到该线程释放资源,将资源状态变成“非锁定”,其他线程才能再次锁定该资源。互斥锁保证每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

from concurrent.futures import ThreadPoolExecutor
from threading import Lock


def thread_run_1(num):
    global global_num
    for i in range(num):
        lock.acquire()  # 上锁
        global_num += 1
        lock.release()  # 解锁
    print("In thread 1, the global_num is {}".format(global_num))

    
def thread_run_2(num):
    global global_num
    for i in range(num):
        lock.acquire()  # 上锁
        global_num += 1
        lock.release()  # 解锁
    print("In thread 2, the global_num is {}".format(global_num))
    
    
if __name__ == "__main__":
    # 定义全变共享变量
    global_num = 0
    # 创建一个互斥锁,默认是上锁状态
    lock = Lock()
    
    with ThreadPoolExecutor() as executor:
        for _ in range(5):
            executor.submit(thread_run_1, 100000)
            executor.submit(thread_run_2, 100000)
    print("Finally, the global_num is {}".format(global_num))

In thread 1, the global_num is 482958
In thread 2, the global_num is 496106
In thread 1, the global_num is 953438
In thread 1, the global_num is 955761
In thread 1, the global_num is 965578
In thread 2, the global_num is 982696
In thread 2, the global_num is 984014
In thread 1, the global_num is 984658
In thread 2, the global_num is 994586In thread 2, the global_num is 1000000

Finally, the global_num is 1000000

Ubuntu环境下的运行情况:

注意:

  • 1、如果这个锁之前没有上锁,那么acquire不会堵塞
  • 2、如果调用acquire对这个锁上锁之前,它已经被其他线程上锁了,那么此时acquire会堵塞,直到这个锁被解锁为止

上锁解锁过程:

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。

每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

小结:

锁的好处:

  • 确保了某段关键代码只能由一个线程从头到尾完整地执行

锁的坏处:

  • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
  • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁

from concurrent.futures import ThreadPoolExecutor
from threading import Lock


def thread_run_1(num):
    global global_num
    for i in range(num):
        lock_1.acquire()  # lock_1 上锁
        global_num += 1
        print("thread_run_1 is locked")
        lock_2.acquire()  # 等待lock_2释放,然后获取上锁,如果lock_2没有释放,一直等待
        print("thread_run_1:get the lock_2")
        lock_2.release()  # lock_2 解锁
        lock_1.release()  # lock_1 解锁
    print("In thread 1, the global_num is {}".format(global_num))

    
def thread_run_2(num):
    global global_num
    for i in range(num):
        lock_2.acquire()  # lock_2 上锁
        global_num += 1
        print("thread_run_2 is locked")
        lock_1.acquire()  # 等待lock_1释放,然后获取上锁,如果lock_1没有释放,一直等待
        print("thread_run_2:get the lock_1")
        lock_1.release()  # lock_1 解锁
        lock_2.release()  # lock_2 解锁
    print("In thread 2, the global_num is {}".format(global_num))
    
              
if __name__ == "__main__":
    global_num = 0
              
    lock_1 = Lock()
    lock_2 = Lock()
              
    with ThreadPoolExecutor() as executor:
        executor.submit(thread_run_1, 10000)
        executor.submit(thread_run_2, 10000)
    print("Finally, the global_num is {}".format(global_num))
thread_run_1 is lockedthread_run_2 is locked

递归锁,可以解决死锁的问题

递归锁: 在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁

lock_1 = lock_2 = threading.RLock(): 一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

from concurrent.futures import ThreadPoolExecutor
from threading import Lock, RLock


def thread_run_1(num):
    global global_num
    for i in range(num):
        lock_1.acquire()  # 上锁,counter加1
        global_num += 1
        print("thread_run_1 is locked")
        lock_2.acquire()  # counter加1
        print("thread_run_1:get the lock_2")
        lock_2.release()  # 解锁,counter减1
        lock_1.release()  # 解锁,counter减1
    print("In thread 1, the global_num is {}".format(global_num))

    
def thread_run_2(num):
    global global_num
    for i in range(num):
        lock_2.acquire()  # 上锁,counter加1
        global_num += 1
        print("thread_run_2 is locked")
        lock_1.acquire()  # counter加1
        print("thread_run_2:get the lock_1")
        lock_1.release()  # 解锁,counter减1
        lock_2.release()  # 解锁,counter减1
    print("In thread 2, the global_num is {}".format(global_num))
    
              
if __name__ == "__main__":
    global_num = 0
    # 解决死锁
    # 这样使用RLock不行,因为这样相当于创建了两个RLock对象
    # lock_1 = RLock()
    # lock_2 = RLock()
    lock_1 = lock_2 = RLock()
              
    with ThreadPoolExecutor() as executor:
        executor.submit(thread_run_1, 1000000)
        executor.submit(thread_run_2, 100000)
    print("Finally, the global_num is {}".format(global_num))

多线程的同步机制-Semaphore(信号对象): 信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。

  • acquire(blocking=True, timeout=None)获取信号。

    • 当blocking=True时:如果调用时计数器大于零,则将其减1并立即返回。如果在调用时计数器为零,则阻塞并等待,直到其他线程调用release()使其大于零。这是通过适当的互锁来完成的,因此如果多个acquire()被阻塞,release()将只唤醒其中一个,这个过程会随机选择一个,因此不应该依赖阻塞线程的被唤醒顺序。返回值为True。
    • 当blocking=False时,不会阻塞。如果调用acquire()时计数器为零,则会立即返回False.
    • 如果设置了timeout参数,它将阻塞最多timeout秒。如果在该时间段内没有获取锁,则返回False,否则返回True。
  • release() 释放信号,使计数器递增1。当计数器为零并有另一个线程等待计数器大于零时,唤醒该线程。

互斥锁,同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

from threading import Semaphore,currentThread
from concurrent.futures import ThreadPoolExecutor
import time,random

def task():
    sm.acquire()
    print('%s is running!' % currentThread().getName())
    time.sleep(random.randint(1,3))
    print('%s is end!' % currentThread().getName())
    sm.release()
    
if __name__ == '__main__':
    sm = Semaphore(5)  # 每次允许5个线程运行
    with ThreadPoolExecutor(max_workers=10) as executor:
        for i in range(10):
            executor.submit(task)

运行结果:

Events:Python提供了Event对象用于线程间通信,它是由线程设置的信号标志,如果信号标志位真,则其他线程等待直到信号接触。

Event对象实现了简单的线程通信机制,它提供了设置信号,清除信号,等待等用于实现线程间的通信。

  • event = threading.Event() 创建一个event
    • 1 设置信号event.set():使用Event的set()方法可以设置Event对象内部的信号标志为真。Event对象提供了isSet()方法来判断其内部信号标志的状态。当使用event对象的set()方法后,isSet()方法返回真
    • 2 清除信号event.clear():使用Event对象的clear()方法可以清除Event对象内部的信号标志,即将其设为假,当使用Event的clear方法后,isSet()方法返回假
    • 3 等待event.wait():Event对象wait的方法只有在内部信号为真的时候才会很快的执行并完成返回。当Event对象的内部信号标志位假时,则wait方法一直等待到其为真时才返回。也就是说必须set新号标志位真

案例:A、B、C三人吃饭,都准备就绪,当收到主人开吃的命令后,ABC三人开吃。

from concurrent.futures import ThreadPoolExecutor
from threading import currentThread, Event
import time 

def lunch(name):
    print("Start thread {} ...".format(currentThread().getName()))
    print("{} is ready!".format(name))
    time.sleep(1)
    event.wait()  # 等待主人发话(堵塞)
    print("Thread {} receive the message...".format(currentThread().getName()))
    print("Friend {} starts to eat!".format(name))
    
if __name__ == "__main__":
    event = Event()
    with ThreadPoolExecutor() as executor:
        for i in "ABC":
            executor.submit(lunch, i)
            
        time.sleep(1)
        print("Main thread send message!")
        event.set()  # 主人发话...

运行结果如下:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值