猪行天下之Python基础——9.2 Python多线程与多进程(中)(三)

简介: 内容简述: 1、threading模块详解 2、queue模块详解

⑦ 通用的条件变量(Event)


Python提供的「用于线程间通信的信号标志」,一个线程标识了一个事件,其他线程处于等待状态,直到事件发生后,所有线程都会被激活。Event对象属性实现了简单的线程通信机制,提供了设置信号,清除信号,等待等用于实现线程间的通信。提供以下四个可供调用的方法:


  • is_set():判断内部标志是否为真


  • set():设置信号标志为真


  • clear():清除Event对象内部的信号标志(设置为false)


  • wait(timeout=None):使线程一直处于堵塞,知道标识符变为True


使用代码示例(汽车等红绿灯的例子):


import threading
import time
import random
class CarThread(threading.Thread):
    def __init__(self, event):
        threading.Thread.__init__(self)
        self.threadEvent = event
    def run(self):
        # 休眠模拟汽车先后到达路口时间
        time.sleep(random.randrange(1, 10))
        print("汽车 - " + self.name + " - 到达路口...")
        self.threadEvent.wait()
        print("汽车 - " + self.name + " - 通过路口...")
if __name__ == '__main__':
    light_event = threading.Event()
    # 假设有20台车子
    for i in range(20):
        car = CarThread(event=light_event)
        car.start()
    while threading.active_count() > 1:
        light_event.clear()
        print("红灯等待...")
        time.sleep(3)
        print("绿灯通行...")
        light_event.set()
        time.sleep(2)


运行结果如下


红灯等待...
汽车 - Thread-10 - 到达路口...
汽车 - Thread-14 - 到达路口...
汽车 - Thread-9 - 到达路口...
汽车 - Thread-11 - 到达路口...
汽车 - Thread-12 - 到达路口...
绿灯通行...
汽车 - Thread-11 - 通过路口...
汽车 - Thread-10 - 通过路口...
汽车 - Thread-9 - 通过路口...
汽车 - Thread-14 - 通过路口...
汽车 - Thread-12 - 通过路口...
汽车 - Thread-6 - 到达路口...
汽车 - Thread-6 - 通过路口...


⑧ 定时器(Timer)


和Thread类似,只是要等待一段时间后才会开始运行,单位秒,用法也很简单,

代码示例如下


import threading
import time
def skill_ready():
    print("菜肴制作完成!!!")
if __name__ == '__main__':
    t = threading.Timer(5, skill_ready)
    t.start()
    while threading.active_count() > 1:
        print("======菜肴制作中======")
        time.sleep(1)


运行结果如下


======菜肴制作中======
======菜肴制作中======
======菜肴制作中======
======菜肴制作中======
======菜肴制作中======
菜肴制作完成!!!


⑨ 栅栏(Barrier)


Barrier直译栅栏,感觉不怎么好理解,我们可以把它看做是赛马用的栅栏,然后马(线程)依次来到栅栏前等待(wait),直到所有的马都停在栅栏面前了,然后所有马开始同时出发(start)。简单点说就是: 多个线程间的相互等待,调用了wait()方法的线程进入堵塞, 直到所有的线程都调用了wait()方法,然后所有线程同时进入就绪状态, 等待调度运行。


构造函数Barrier(parties,action=None,timeout=None)


参数解释


  • parties:创建一个可容纳parties条线程的栅栏;


  • action:全部线程被释放时可被其中一条线程调用的可调用对象;


  • timeout:线程调用wait()方法时没有显式设定timeout,就用的这个作为默认值;


相关属性与函数


  • wait(timeout=None):表示线程就位,返回值是一个0到parties-1之间的
    整数, 每条线程都不一样,这个值可以用作挑选一条线程做些清扫工作,另外如果你在构造函数里设置了action的话,其中一个线程在释放之前将会调用它。如果调用出错的话,会让栅栏进入broken状态,超时同样也会进入broken状态,如果栅栏在处于broke状态的时候调用reset函数,会抛出一个BrokenBarrierError异常。


  • reset():本方法将栅栏置为初始状态,即empty状态。所有已经在等待的线程都会接收到BrokenBarrierError异常,注意当有其他处于unknown状态的线程时,调用此方法将可能获取到额外的访问。因此如果一个栅栏进入了broken状态, 最好是放弃他并新建一个栅栏,而不是调用reset方法。


  • abort():将栅栏置为broken状态。本方法将使所有正在等待或将要调用
    wait()方法的线程收到BrokenBarrierError异常。本方法的使用情景为,比如:
    有一条线程需要abort(),又不想给其他线程造成死锁的状态,或许设定
    timeout参数要比使用本方法更可靠。


  • parites:将要使用本 barrier 的线程的数量


  • n_waiting:正在等待本 barrier 的线程的数量


  • broken:栅栏是否为broken状态,返回一个布尔值


  • BrokenBarrierError:RuntimeError的子类,当栅栏被reset()或broken时引发;


使用代码示例如下(公司一起去旅游等人齐才出发):


import threading
import time
import random
class Staff(threading.Thread):
    def __init__(self, barriers):
        threading.Thread.__init__(self)
        self.barriers = barriers
    def run(self):
        print("员工 【" + self.name + "】" + "出门")
        time.sleep(random.randrange(1, 10))
        print("员工 【" + self.name + "】" + "已签到")
        self.barriers.wait()
def ready():
    print(threading.current_thread().name + ":人齐,出发,出发~~~")
if __name__ == '__main__':
    print("要出去旅游啦,大家快集合~")
    b = threading.Barrier(10, action=ready, timeout=20)
    for i in range(10):
        staff = Staff(b)
        staff.start()


运行结果如下


要出去旅游啦,大家快集合~
员工 【Thread-1】出门
员工 【Thread-2】出门
员工 【Thread-3】出门
员工 【Thread-4】出门
员工 【Thread-5】出门
员工 【Thread-6】出门
员工 【Thread-7】出门
员工 【Thread-8】出门
员工 【Thread-9】出门
员工 【Thread-10】出门
员工 【Thread-8】已签到
员工 【Thread-4】已签到
员工 【Thread-5】已签到
员工 【Thread-6】已签到
员工 【Thread-9】已签到
员工 【Thread-2】已签到
员工 【Thread-3】已签到
员工 【Thread-7】已签到
员工 【Thread-1】已签到
员工 【Thread-10】已签到
Thread-10:人齐,出发,出发~~~


2、queue模块详解


Python中的queue模块中已经实现了一个线程安全的多生产者,多消费者队列,自带锁,常用于多线程并发数据交换。内置三种类型的队列:


  • Queue:FIFO(先进先出);


  • LifoQueue:LIFO(后进先出);


  • PriorityQueue:优先级最小的先出;


三种类型的队列的构造函数都是(maxsize=0),用于设置队列容量,如果设置的maxsize小于1,则表示队列的长度无限长。


两个异常


  • Queue.Empty:当调用非堵塞的get()获取空队列元素时会引发;


  • Queue.Full:当调用非堵塞的put()满队列里添加元素时会引发;


相关函数


  • size():返回队列的近似大小,注意:qsize()> 0不保证随后的get()不会 阻塞也不保证qsize() < maxsize后的put()不会堵塞;


  • empty():判断队列是否为空,返回布尔值,如果返回True,不保证后续 调用put()不会阻塞,同理,返回False也不保证get()调用不会被阻塞;


  • full():判断队列是否满,返回布尔值如果返回True,不保证后续 调用get()不会阻塞,同理,返回False也不保证put()调用不会被阻塞;


  • put(item, block=True, timeout=None):往队列中放入元素,如果block为True且timeout参数为None(默认),为堵塞型put(),如果timeout是 正数,会堵塞timeout时间并引发Queue.Full异常,如果block为False则 为非堵塞put()。


  • put_nowait(item):等价于put(item, False),非堵塞put()


  • get(block=True, timeout=None):移除一个队列元素,并返回该元素,如果block为True表示堵塞函数,block = False为非堵塞函数,如果设置了timeout,堵塞时最多堵塞超过多少秒,如果这段时间内没有可用的项,会引发Queue.Empty异常,如果为非堵塞状态,有数据可用返回数据无数据立即抛出Queue.Empty异常;


  • get_nowait():等价于get(False),非堵塞get()


  • task_done():完成一项工作后,调用该方法向队列发送一个完成信号,任务-1;


  • join():等队列为空,再执行别的操作;


使用代码示例如下


import threading
import queue
import time
import random
work_queue = queue.Queue()
# 任务模拟
def working():
    global work_queue
    while not work_queue.empty():
        data = work_queue.get()
        time.sleep(random.randrange(1, 2))
        print("执行" + data)
        work_queue.task_done()
# 工作线程
class WorkThread(threading.Thread):
    def __init__(self, t_name, func):
        self.func = func
        threading.Thread.__init__(self, name=t_name)
    def run(self):
        self.func()
if __name__ == '__main__':
    work_list = []
    for i in range(1, 21):
        work_list.append("任务 %d" % i)
    # 模拟把需要执行的任务放到队列中
    for i in work_list:
        work_queue.put(i)
    # 初始化一个线程列表
    threads = []
    for i in range(0, len(work_list)):
        t = WorkThread(t_name="线程" + str(i), func=working)
        t.daemon = True
        t.start()
        threads.append(t)
    work_queue.join()
    for t in threads:
        t.join()
    print("所有任务执行完毕")


运行结果如下


执行任务 1
执行任务 3
执行任务 5
执行任务 2
执行任务 4
执行任务 6
执行任务 8
执行任务 10
执行任务 13
执行任务 11
执行任务 17
执行任务 18
执行任务 19
执行任务 7
执行任务 14
执行任务 16
执行任务 9
执行任务 15
执行任务 12
执行任务 20
所有任务执行完毕


目录
打赏
0
0
0
0
7
分享
相关文章
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
157 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
23天前
|
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
47 20
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
19 0
如何区分进程、线程和协程?看这篇就够了!
本课程主要探讨操作系统中的进程、线程和协程的区别。进程是资源分配的基本单位,具有独立性和隔离性;线程是CPU调度的基本单位,轻量且共享资源,适合并发执行;协程更轻量,由程序自身调度,适合I/O密集型任务。通过学习这些概念,可以更好地理解和应用它们,以实现最优的性能和资源利用。
79 11
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
48 6
【C语言】进程和线程详解
在现代操作系统中,进程和线程是实现并发执行的两种主要方式。理解它们的区别和各自的应用场景对于编写高效的并发程序至关重要。
98 6
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
5天前
|
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
29 5