Python的并发并行[1] -> 线程[3] -> 多线程的同步控制
多线程的控制方式
目录
1 唤醒单个线程等待
Condition类相当于一把高级的锁,可以进行一些复杂的线程同步控制。一般Condition内部都有一把内置的锁对象(默认为RLock),对于Condition的使用主要有以下步骤:
- 建立两个线程对象,及Condition对象;
- 线程1首先获取Condition的锁权限,acquire();
- 线程1执行需要完成的任务后,调用等待wait(),此时,线程1会阻塞挂起,出让内置锁的控制权(即Condition可被其他线程acquire);
- 线程2对Condition内置锁的权限进行获取acquire(),当线程2执行需要完成的任务后,会调用唤醒,notify(),但此时线程1并不会立即被唤醒继续执行,而是要等待线程2释放Condition的内置锁release()之后,线程1才会唤醒;
- 线程2释放锁release()之后,线程1自动重新对锁进行占有,直到线程1完成所有任务后,再执行释放锁release(),从而结束。
- from threading import Thread, Condition
- import time
- cond = Condition()
- class Sleeper(Thread):
- def __init__(self):
- super(Sleeper, self).__init__()
- def run(self):
- print('Sleeper gets into room, starts sleeping')
- cond.acquire()
- print('Sleeper is sleeping, waiting for wakeup')
- cond.wait()
- print('Sleeper is waked up')
- cond.release()
- print('Sleeper out of room')
- class Waker(Thread):
- def __init__(self):
- super(Waker, self).__init__()
- def run(self):
- print('Waker waiting sleeper getting into room and sleeping...')
- time.sleep(1)
- cond.acquire()
- print('Waker gets into the room')
- cond.notify()
- print('Waker trying to wake up sleeper')
- time.sleep(3)
- print('Waker finished wake up, leave the room')
- cond.release()
- sleeper = Sleeper()
- waker = Waker()
- sleeper.start()
- waker.start()
上面的代码中,首先导入所需的模块,生成Condition的实例,之后派生两个线程模拟sleeper和waker,其中sleeper会先对Condition进行获取权限,随后进入等待,而waker会在sleeper进入等待后,获取Condition的权限,然后尝试唤醒sleeper,随后释放Condition锁,将占有权归还,sleeper收到归还的权限后,调用release进行释放。
运行得到结果
- Sleeper gets into room, starts sleeping
- Sleeper is sleeping, waiting for wakeup
- Waker waiting sleeper getting into room and sleeping...
- Waker gets into the room
- Waker trying to wake up sleeper
- Waker finished wake up, leave the room
- Sleeper is waked up
- Sleeper out of room
从输出的结果中可以看到,waker在notify了sleeper之后,等待了3秒,而这三秒内,sleeper并未被彻底唤醒,而是等待waker的release()之后,sleeper才被真正唤醒继续执行。
2 唤醒多个线程等待
当有多个线程进入Condition的条件等待时,可以使用两种方式进行唤醒,第一种是利用等数量的线程唤醒,第二种是利用notify_all()函数唤醒所有线程。
下面的例子对比了两种唤醒方式
- from threading import Thread, Condition
- import random
- import time
- cond = Condition()
- class Sleeper(Thread):
- def __init__(self):
- super(Sleeper, self).__init__()
- self.num = self.name[-1]
- print('Sleeper_%s ready' % self.num)
- def run(self):
- print('Sleeper_%s gets into room, starts sleeping' % self.num)
- cond.acquire()
- print('Sleeper_%s is sleeping, waiting for wakeup' % self.num)
- cond.wait()
- print('Sleeper_%s is waked up' % self.num)
- cond.release()
- print('Sleeper_%s out of room' % self.num)
- class Waker(Thread):
- def __init__(self, all=False):
- super(Waker, self).__init__()
- self.all = all
- self.num = self.name[-1]
- print('Waker_%s ready' % self.num)
- def run(self):
- print('Waker_%s waiting sleeper getting into room and sleeping...' % self.num)
- time.sleep(1)
- cond.acquire()
- print('Waker_%s gets into the room' % self.num)
- if self.all:
- cond.notify_all()
- else:
- cond.notify()
- print('Waker_%s trying to wake up sleeper' % self.num)
- time.sleep(3)
- print('Waker_%s finished wake up, leave the room' % self.num)
- cond.release()
- if __name__ == '__main__':
- print('-------Order Wake-------')
- sleepers = []
- wakers = []
- for i in range(3):
- sleepers.append(Sleeper())
- for i in range(3):
- wakers.append(Waker())
- random.shuffle(sleepers)
- random.shuffle(wakers)
- for s in sleepers:
- s.start()
- for s in wakers:
- s.start()
- for s in sleepers:
- s.join()
- print('-------All Wake-------')
- sleepers = []
- for i in range(3):
- sleepers.append(Sleeper())
- waker = Waker(all=True)
- random.shuffle(sleepers)
- for s in sleepers:
- s.start()
- waker.start()
上面的代码中,在导入所需模块后,定义Sleeper和Waker类,并让他们拥有各自名字,在主函数中首先利用单个唤醒的方式去唤醒所有线程,随后再利用全部唤醒的方式唤醒所有线程,其中随机数模块用于重新排序Sleeper。
运行得到结果
- -------Order Wake-------
- Sleeper_1 ready
- Sleeper_2 ready
- Sleeper_3 ready
- Waker_4 ready
- Waker_5 ready
- Waker_6 ready
- Sleeper_1 gets into room, starts sleeping
- Sleeper_1 is sleeping, waiting for wakeup
- Sleeper_3 gets into room, starts sleeping
- Sleeper_3 is sleeping, waiting for wakeup
- Sleeper_2 gets into room, starts sleeping
- Sleeper_2 is sleeping, waiting for wakeup
- Waker_4 waiting sleeper getting into room and sleeping...
- Waker_6 waiting sleeper getting into room and sleeping...
- Waker_5 waiting sleeper getting into room and sleeping...
- Waker_6 gets into the room
- Waker_6 trying to wake up sleeper
- Waker_6 finished wake up, leave the room
- Waker_5 gets into the room
- Waker_5 trying to wake up sleeper
- Waker_5 finished wake up, leave the room
- Sleeper_1 is waked up
- Sleeper_1 out of room
- Waker_4 gets into the room
- Waker_4 trying to wake up sleeper
- Waker_4 finished wake up, leave the room
- Sleeper_3 is waked up
- Sleeper_3 out of room
- Sleeper_2 is waked up
- Sleeper_2 out of room
- -------All Wake-------
- Sleeper_7 ready
- Sleeper_8 ready
- Sleeper_9 ready
- Waker_0 ready
- Sleeper_9 gets into room, starts sleeping
- Sleeper_9 is sleeping, waiting for wakeup
- Sleeper_7 gets into room, starts sleeping
- Sleeper_7 is sleeping, waiting for wakeup
- Sleeper_8 gets into room, starts sleeping
- Sleeper_8 is sleeping, waiting for wakeup
- Waker_0 waiting sleeper getting into room and sleeping...
- Waker_0 gets into the room
- Waker_0 trying to wake up sleeper
- Waker_0 finished wake up, leave the room
- Sleeper_9 is waked up
- Sleeper_9 out of room
- Sleeper_7 is waked up
- Sleeper_7 out of room
- Sleeper_8 is waked up
- Sleeper_8 out of room
从输出的结果中可以看出,
- Sleeper按顺序生成,按乱序启动;
- Waker的单次唤醒,首先唤醒最先进入等待的线程,而不是最先生成的,即唤醒的顺序是按照acquire后进入wait的顺序先后进行唤醒(出让锁控制权的顺序);
- Waker的notify_all函数能够一次唤醒所有等待线程,顺序与单个唤醒相同。
3 条件函数等待
利用Condition类中的wait_for函数可以实现一种条件等待,当等待的函数返回值为真的时候,线程会被唤醒并继续执行。查看wait_for源码可以看出,调用wait_for函数之后,会对传入的函数进行调用,若返回结果为True,则返回True,或返回结果为False,则进入循环中,通过对时间限制的判断,最终阻塞在wait函数上,直到超时后再次获取传入函数的返回值,若依旧False,则会由于超时判断而退出循环。
下面的例子中定义了一个等待类,以及alarm函数,waiter会在启动后调用条件等待,等待函数为alarm,当alarm返回为真后,继续函数。
- from threading import Thread, Condition
- import time
- cond = Condition()
- class Waiter(Thread):
- def __init__(self, alarm):
- super(Waiter, self).__init__()
- self.alarm = alarm
- print('Waiter ready')
- def run(self):
- cond.acquire()
- print('Waiter waiting for alarm...')
- cond.wait_for(self.alarm, timeout=1)
- print('Waiter received alarm')
- cond.release()
- print('Waiter completed')
- def alarm():
- print('Sending alarm...')
- time.sleep(5)
- print('alarm sent')
- return True
- waiter = Waiter(alarm)
- waiter.run()
运行得到结果
- Waiter ready
- Waiter waiting for alarm...
- Sending alarm...
- alarm sent
- Waiter received alarm
- Waiter completed
从输出的结果可以看出,waiter也会等待alarm函数结束返回结果后再继续进行
4 事件触发标志
Event与Condition类似,实质上是一个对内置Flag监测的事件标志触发类,在生成的Event实例内,会由一个内置Flag,初始状态为False,当Event类调用wait函数时,会查看内置Flag的状态,若为True则不会阻塞,若为False则调用内置Condition类的wait函数等待唤醒。
通过对源码的查看可以看出,实质上
- Event的wait函数是利用Flag以及Condition的wait函数实现;
- Event的set函数是利用Condition的with(acquire, release)获得控制,并将Flag置为Ture,同时notify_all();
- Event的clear函数同样是利用Condition的with获得控制,然后将标志Flag置为False。
下面的例子利用了set, wait, clear函数实现了一个传球模拟。
- from threading import Thread, Event
- import time
- evt = Event()
- class Mate_1(Thread):
- def run(self):
- print('Mate_1 is running')
- time.sleep(1)
- print('Mate_1 got the ball')
- time.sleep(1)
- evt.set()
- print('Mate_1 pass the ball to others')
- evt.clear()
- evt.wait()
- time.sleep(1)
- print('Mate_1 got the ball again')
- time.sleep(1)
- print('Mate_1 makes a goal')
- evt.set()
- class Mate_2(Thread):
- def run(self):
- print('Mate_2 is running')
- if evt.is_set():
- evt.clear()
- evt.wait()
- time.sleep(1)
- print('Mate_2 got the ball')
- time.sleep(1)
- evt.set()
- print('Mate_2 pass the ball to others')
- time.sleep(1)
- evt.clear()
- evt.wait()
- time.sleep(1)
- print('Mate_2 hugs Mate_1 for congratulation')
- if __name__ == '__main__':
- m = Mate_1()
- n = Mate_2()
- m.start()
- n.start()
上面的代码中,首先建立事件类实例,派生两个球员子线程,球员1会在开始后1秒收到足球,控球1秒后通过evt.set()唤醒另一个线程,模拟将球传出的过程,随后清除标志进入等待。此时球员2原本处于wait状态,收到set将内置Flag重置的信号后,被唤醒,表明自己接到球,控球1秒后再set传回,自己进入等待,球员1接到信号再次接球并得分后通知球员2,球员2则在收到进球信号后表示庆祝。
运行得到结果
- Mate_1 is running
- Mate_2 is running
- Mate_1 got the ball
- Mate_1 pass the ball to others
- Mate_2 got the ball
- Mate_2 pass the ball to others
- Mate_1 got the ball again
- Mate_1 makes a goal
- Mate_2 hugs Mate_1 for congratulation
结果中可以看出,以 set 方法模拟传球,使两个线程间互相配合执行。
5 函数延迟启动
利用Timer类可以实现对函数的延时启动,以及在未启动前取消启动的操作
- from threading import Timer
- from time import ctime, sleep
- def func():
- print('Current time is', ctime(), 'Hello world')
- timex = Timer(3, func)
- print('Current time is', ctime())
- timex.start()
- timex.join()
- timex = Timer(3, func)
- print('Current time is', ctime())
- timex.start()
- sleep(1)
- timex.cancel()
- print('End')
运行得到结果
- Current time is Thu Aug 3 16:22:19 2017
- Current time is Thu Aug 3 16:22:22 2017 Hello world
- Current time is Thu Aug 3 16:22:22 2017
- End
从结果中可以看出,延时启动3秒起到了作用,而第二次的函数则在调用前被取消了
6 设置线程障碍
对于多线程来说,可以利用Barrier类实现对指定数量的线程进行阻碍,直到线程数量达到指定值后,同时释放所有的线程。
- from threading import Thread, Barrier
- import random
- import time
- COUNT = 0
- def action():
- print('ACTION')
- def foo():
- global COUNT
- time.sleep(random.randint(1, 7))
- print('Barrier_%d waiting' % COUNT)
- COUNT += 1
- barr.wait()
- print('Hello, world', COUNT)
- barr = Barrier(7, action=action, timeout=20)
- threads = []
- for i in range(7):
- threads.append(Thread(target=foo))
- for t in threads:
- t.start()
- for t in threads:
- t.join()
- print('Pass barrier')
第 1-15 行,导入所需模块后,定义一个执行函数,用于越过障碍时执行,再定义一个线程函数,用于线程执行。线程执行函数会在进入后对全局变量+1,随后进入阻塞状态。
第 16 行,设置障碍上限,且线程数量与障碍上限相同。若此处线程数量大于障碍数,则跨过障碍后,多出的两个障碍会一直阻塞。可考虑使用reset函数重置计数,达到循环。
运行得到结果
- Barrier_0 waiting
- Barrier_1 waiting
- Barrier_2 waiting
- Barrier_3 waiting
- Barrier_4 waiting
- Barrier_5 waiting
- Barrier_6 waiting
- ACTION
- Hello, world 7
- Hello, world 7
- Hello, world 7
- Hello, world 7
- Hello, world 7
- Hello, world 7
- Hello, world 7
- Pass barrier
从输出的结果可以看到,当障碍数量达到上限的时候,会运行执行函数,随后唤醒所有的线程。
相关阅读
1. 基本概念
2. threading 模块
3. 多线程的建立
4. 锁与信号量
参考链接
《Python 核心编程 第3版》
Python的并发并行[1] -> 线程[3] -> 多线程的同步控制的更多相关文章
- Python的并发并行[1] -> 线程[1] -> 多线程的建立与使用
多线程的建立与使用 目录 生成线程的三种方法 单线程与多线程对比 守护线程的设置 1 生成线程的三种方法 三种方式分别为: 创建一个Thread实例,传给它一个函数 创建一个Thread实例,传给它一 ...
- Python的并发并行[1] -> 线程[2] -> 锁与信号量
锁与信号量 目录 添加线程锁 锁的本质 互斥锁与可重入锁 死锁的产生 锁的上下文管理 信号量与有界信号量 1 添加线程锁 由于多线程对资源的抢占顺序不同,可能会产生冲突,通过添加线程锁来对共有资源进行 ...
- Python的并发并行[1] -> 线程[0] -> threading 模块
threading模块 / threading Module 1 常量 / Constants Pass 2 函数 / Function 2.1 setprofile()函数 函数调用: thread ...
- 并发 并行 进程 线程 协程 异步I/O python async
一些草率不精确的观点: 并发: 一起发生,occurence: sth that happens. 并行: 同时处理. parallel lines: 平行线.thread.join()之前是啥?落霞 ...
- Python 之并发编程之线程下
七.线程局部变量 多线程之间使用threading.local 对象用来存储数据,而其他线程不可见 实现多线程之间的数据隔离 本质上就是不同的线程使用这个对象时,为其创建一个只属于当前线程的字典 拿空 ...
- Python的并发并行[4] -> 并发[0] -> 利用线程池启动线程
利用线程池启动线程 submit与map启动线程 利用两种方式分别启动线程,同时利用with上下文管理来对线程池进行控制 from concurrent.futures import ThreadPo ...
- Python 之并发编程之线程上
一.线程概念 进程是资源分配的最小单位 线程是计算机中调度的最小单位 多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都 ...
- Python的并发并行[0] -> 基本概念
基本概念 / Basic Concept 快速跳转 进程 / Process 线程 / Thread 协程 / Coroutine 全局解释器锁 / Global Interpreter Lock ...
- [并发并行]_[线程模型]_[Pthread线程使用模型之三 客户端/服务端模型(Client/Server]
Pthread线程使用模型之三 客户端/服务端模型(Client/Server) 场景 1.在客户端/服务端模型时,客户端向服务端请求一些数据集的操作. 服务端执行执行操作独立的(多进程或跨网络)– ...
随机推荐
- DOS程序员手册(九)
第14章参考手册概述 本书余下的章节将向读者们介绍BIOS.DOS各种各样API函数和服务,作为一名程 序员,了解和掌握这些知识是很有好处的.在所介绍的参考手册中,每部手册都汇集了大 量的资源 ...
- 【Linear Models for Binary Classification】林轩田机器学习基石
首先回顾了几个Linear Model的共性:都是算出来一个score,然后做某种变化处理. 既然Linear Model有各种好处(训练时间,公式简单),那如何把Linear Regression给 ...
- 遍历两个自定义列表来实现字典(key:value)
#自定义key ${keys} create list key1 key2 key3 #自定义value ${values} create list v ...
- 浅谈 css 之 position用法
在 css中, position 属性有四个值可用: static(默认值).absolute.relative.fixed. relative:相对定位(相对于自身进行在常规流中的位置进行定位,保留 ...
- GLIBCXX3.4.21 not find
在执行世界杯的二进制代码和安装keepaway中会遇到GLIBCXX3.4.21 not find的问题,其解决办法就是升级安装GCC. 一.首先查看当前gcc版本 $ strings /usr/li ...
- shell之iptables
这五个位置也被称为五个钩子函数(hook functions),也叫五个规则链. 1.PREROUTING (路由前) 2.INPUT (数据包流入口) 3.FORWARD (转发管卡) 4.OUTP ...
- HDU 4472 Count (DP)
题目:问n个节点构成完全对称的树有多少种方法. 因为树是完全对称的,所以它的子树也是完全对称的. 对于每个树,拿出一个根节点,枚举剩下的节点能拆分成多少个子树. #include <cstdio ...
- 软工实践Alpha冲刺(9/10)
v队名:起床一起肝活队 组长博客:博客链接 作业博客:班级博客本次作业的链接 组员情况 组员1(队长):白晨曦 过去两天完成了哪些任务 描述: 已经解决登录注册等基本功能的界面. 完成非功能的主界面制 ...
- ajax的多次请求问题
我们在用ajax请求数据时,可能会遇到一次点击多次触发的可能.(比如说:ajax 的 onreadystatechange 事件就会触发多次:这是因为 onreadystatechange 是一个事件 ...
- SQL查询oracle的nclob字段
使用CONTAINS关键字查询NCLOB字段 SELECT FORMATTED_MESSAGE FROM TBL_LOG WHERE CONTAINS(FORMATTED_ME ...