多线程的控制方式


目录

  1. 唤醒单个线程等待
  2. 唤醒多个线程等待
  3. 条件函数等待
  4. 事件触发标志
  5. 函数延迟启动
  6. 设置线程障碍

1 唤醒单个线程等待

Condition类相当于一把高级的锁,可以进行一些复杂的线程同步控制。一般Condition内部都有一把内置的锁对象(默认为RLock),对于Condition的使用主要有以下步骤:

  1. 建立两个线程对象,及Condition对象;
  2. 线程1首先获取Condition的锁权限,acquire();
  3. 线程1执行需要完成的任务后,调用等待wait(),此时,线程1会阻塞挂起,出让内置锁的控制权(即Condition可被其他线程acquire);
  4. 线程2对Condition内置锁的权限进行获取acquire(),当线程2执行需要完成的任务后,会调用唤醒,notify(),但此时线程1并不会立即被唤醒继续执行,而是要等待线程2释放Condition的内置锁release()之后,线程1才会唤醒;
  5. 线程2释放锁release()之后,线程1自动重新对锁进行占有,直到线程1完成所有任务后,再执行释放锁release(),从而结束。
  1. from threading import Thread, Condition
  2. import time
  3. cond = Condition()
  4.  
  5. class Sleeper(Thread):
  6. def __init__(self):
  7. super(Sleeper, self).__init__()
  8.  
  9. def run(self):
  10. print('Sleeper gets into room, starts sleeping')
  11. cond.acquire()
  12. print('Sleeper is sleeping, waiting for wakeup')
  13. cond.wait()
  14. print('Sleeper is waked up')
  15. cond.release()
  16. print('Sleeper out of room')
  17.  
  18. class Waker(Thread):
  19. def __init__(self):
  20. super(Waker, self).__init__()
  21.  
  22. def run(self):
  23. print('Waker waiting sleeper getting into room and sleeping...')
  24. time.sleep(1)
  25. cond.acquire()
  26. print('Waker gets into the room')
  27. cond.notify()
  28. print('Waker trying to wake up sleeper')
  29. time.sleep(3)
  30. print('Waker finished wake up, leave the room')
  31. cond.release()
  32.  
  33. sleeper = Sleeper()
  34. waker = Waker()
  35. sleeper.start()
  36. waker.start()

上面的代码中,首先导入所需的模块,生成Condition的实例,之后派生两个线程模拟sleeper和waker,其中sleeper会先对Condition进行获取权限,随后进入等待,而waker会在sleeper进入等待后,获取Condition的权限,然后尝试唤醒sleeper,随后释放Condition锁,将占有权归还,sleeper收到归还的权限后,调用release进行释放。

运行得到结果

  1. Sleeper gets into room, starts sleeping
  2. Sleeper is sleeping, waiting for wakeup
  3. Waker waiting sleeper getting into room and sleeping...
  4. Waker gets into the room
  5. Waker trying to wake up sleeper
  6. Waker finished wake up, leave the room
  7. Sleeper is waked up
  8. Sleeper out of room

从输出的结果中可以看到,waker在notify了sleeper之后,等待了3秒,而这三秒内,sleeper并未被彻底唤醒,而是等待waker的release()之后,sleeper才被真正唤醒继续执行。

2 唤醒多个线程等待

当有多个线程进入Condition的条件等待时,可以使用两种方式进行唤醒,第一种是利用等数量的线程唤醒,第二种是利用notify_all()函数唤醒所有线程。

下面的例子对比了两种唤醒方式

  1. from threading import Thread, Condition
  2. import random
  3. import time
  4. cond = Condition()
  5.  
  6. class Sleeper(Thread):
  7. def __init__(self):
  8. super(Sleeper, self).__init__()
  9. self.num = self.name[-1]
  10. print('Sleeper_%s ready' % self.num)
  11.  
  12. def run(self):
  13. print('Sleeper_%s gets into room, starts sleeping' % self.num)
  14. cond.acquire()
  15. print('Sleeper_%s is sleeping, waiting for wakeup' % self.num)
  16. cond.wait()
  17. print('Sleeper_%s is waked up' % self.num)
  18. cond.release()
  19. print('Sleeper_%s out of room' % self.num)
  20.  
  21. class Waker(Thread):
  22. def __init__(self, all=False):
  23. super(Waker, self).__init__()
  24. self.all = all
  25. self.num = self.name[-1]
  26. print('Waker_%s ready' % self.num)
  27.  
  28. def run(self):
  29. print('Waker_%s waiting sleeper getting into room and sleeping...' % self.num)
  30. time.sleep(1)
  31. cond.acquire()
  32. print('Waker_%s gets into the room' % self.num)
  33. if self.all:
  34. cond.notify_all()
  35. else:
  36. cond.notify()
  37. print('Waker_%s trying to wake up sleeper' % self.num)
  38. time.sleep(3)
  39. print('Waker_%s finished wake up, leave the room' % self.num)
  40. cond.release()
  41.  
  42. if __name__ == '__main__':
  43. print('-------Order Wake-------')
  44. sleepers = []
  45. wakers = []
  46. for i in range(3):
  47. sleepers.append(Sleeper())
  48. for i in range(3):
  49. wakers.append(Waker())
  50.  
  51. random.shuffle(sleepers)
  52. random.shuffle(wakers)
  53.  
  54. for s in sleepers:
  55. s.start()
  56. for s in wakers:
  57. s.start()
  58. for s in sleepers:
  59. s.join()
  60.  
  61. print('-------All Wake-------')
  62. sleepers = []
  63. for i in range(3):
  64. sleepers.append(Sleeper())
  65. waker = Waker(all=True)
  66.  
  67. random.shuffle(sleepers)
  68.  
  69. for s in sleepers:
  70. s.start()
  71. waker.start()

上面的代码中,在导入所需模块后,定义Sleeper和Waker类,并让他们拥有各自名字,在主函数中首先利用单个唤醒的方式去唤醒所有线程,随后再利用全部唤醒的方式唤醒所有线程,其中随机数模块用于重新排序Sleeper。

运行得到结果

  1. -------Order Wake-------
  2. Sleeper_1 ready
  3. Sleeper_2 ready
  4. Sleeper_3 ready
  5. Waker_4 ready
  6. Waker_5 ready
  7. Waker_6 ready
  8. Sleeper_1 gets into room, starts sleeping
  9. Sleeper_1 is sleeping, waiting for wakeup
  10. Sleeper_3 gets into room, starts sleeping
  11. Sleeper_3 is sleeping, waiting for wakeup
  12. Sleeper_2 gets into room, starts sleeping
  13. Sleeper_2 is sleeping, waiting for wakeup
  14. Waker_4 waiting sleeper getting into room and sleeping...
  15. Waker_6 waiting sleeper getting into room and sleeping...
  16. Waker_5 waiting sleeper getting into room and sleeping...
  17. Waker_6 gets into the room
  18. Waker_6 trying to wake up sleeper
  19. Waker_6 finished wake up, leave the room
  20. Waker_5 gets into the room
  21. Waker_5 trying to wake up sleeper
  22. Waker_5 finished wake up, leave the room
  23. Sleeper_1 is waked up
  24. Sleeper_1 out of room
  25. Waker_4 gets into the room
  26. Waker_4 trying to wake up sleeper
  27. Waker_4 finished wake up, leave the room
  28. Sleeper_3 is waked up
  29. Sleeper_3 out of room
  30. Sleeper_2 is waked up
  31. Sleeper_2 out of room
  32. -------All Wake-------
  33. Sleeper_7 ready
  34. Sleeper_8 ready
  35. Sleeper_9 ready
  36. Waker_0 ready
  37. Sleeper_9 gets into room, starts sleeping
  38. Sleeper_9 is sleeping, waiting for wakeup
  39. Sleeper_7 gets into room, starts sleeping
  40. Sleeper_7 is sleeping, waiting for wakeup
  41. Sleeper_8 gets into room, starts sleeping
  42. Sleeper_8 is sleeping, waiting for wakeup
  43. Waker_0 waiting sleeper getting into room and sleeping...
  44. Waker_0 gets into the room
  45. Waker_0 trying to wake up sleeper
  46. Waker_0 finished wake up, leave the room
  47. Sleeper_9 is waked up
  48. Sleeper_9 out of room
  49. Sleeper_7 is waked up
  50. Sleeper_7 out of room
  51. Sleeper_8 is waked up
  52. Sleeper_8 out of room

从输出的结果中可以看出,

  1. Sleeper按顺序生成,按乱序启动;
  2. Waker的单次唤醒,首先唤醒最先进入等待的线程,而不是最先生成的,即唤醒的顺序是按照acquire后进入wait的顺序先后进行唤醒(出让锁控制权的顺序);
  3. Waker的notify_all函数能够一次唤醒所有等待线程,顺序与单个唤醒相同。

3 条件函数等待

利用Condition类中的wait_for函数可以实现一种条件等待,当等待的函数返回值为真的时候,线程会被唤醒并继续执行。查看wait_for源码可以看出,调用wait_for函数之后,会对传入的函数进行调用,若返回结果为True,则返回True,或返回结果为False,则进入循环中,通过对时间限制的判断,最终阻塞在wait函数上,直到超时后再次获取传入函数的返回值,若依旧False,则会由于超时判断而退出循环。

下面的例子中定义了一个等待类,以及alarm函数,waiter会在启动后调用条件等待,等待函数为alarm,当alarm返回为真后,继续函数。

  1. from threading import Thread, Condition
  2. import time
  3. cond = Condition()
  4.  
  5. class Waiter(Thread):
  6. def __init__(self, alarm):
  7. super(Waiter, self).__init__()
  8. self.alarm = alarm
  9. print('Waiter ready')
  10.  
  11. def run(self):
  12. cond.acquire()
  13. print('Waiter waiting for alarm...')
  14. cond.wait_for(self.alarm, timeout=1)
  15. print('Waiter received alarm')
  16. cond.release()
  17. print('Waiter completed')
  18.  
  19. def alarm():
  20. print('Sending alarm...')
  21. time.sleep(5)
  22. print('alarm sent')
  23. return True
  24.  
  25. waiter = Waiter(alarm)
  26. waiter.run()

运行得到结果

  1. Waiter ready
  2. Waiter waiting for alarm...
  3. Sending alarm...
  4. alarm sent
  5. Waiter received alarm
  6. Waiter completed

从输出的结果可以看出,waiter也会等待alarm函数结束返回结果后再继续进行

4 事件触发标志

Event与Condition类似,实质上是一个对内置Flag监测的事件标志触发类,在生成的Event实例内,会由一个内置Flag,初始状态为False,当Event类调用wait函数时,会查看内置Flag的状态,若为True则不会阻塞,若为False则调用内置Condition类的wait函数等待唤醒。

通过对源码的查看可以看出,实质上

  1. Event的wait函数是利用Flag以及Condition的wait函数实现;
  2. Event的set函数是利用Condition的with(acquire, release)获得控制,并将Flag置为Ture,同时notify_all();
  3. Event的clear函数同样是利用Condition的with获得控制,然后将标志Flag置为False。

下面的例子利用了set, wait, clear函数实现了一个传球模拟。

  1. from threading import Thread, Event
  2. import time
  3.  
  4. evt = Event()
  5. class Mate_1(Thread):
  6. def run(self):
  7. print('Mate_1 is running')
  8. time.sleep(1)
  9. print('Mate_1 got the ball')
  10. time.sleep(1)
  11. evt.set()
  12. print('Mate_1 pass the ball to others')
  13. evt.clear()
  14. evt.wait()
  15. time.sleep(1)
  16. print('Mate_1 got the ball again')
  17. time.sleep(1)
  18. print('Mate_1 makes a goal')
  19. evt.set()
  20.  
  21. class Mate_2(Thread):
  22. def run(self):
  23. print('Mate_2 is running')
  24. if evt.is_set():
  25. evt.clear()
  26. evt.wait()
  27. time.sleep(1)
  28. print('Mate_2 got the ball')
  29. time.sleep(1)
  30. evt.set()
  31. print('Mate_2 pass the ball to others')
  32. time.sleep(1)
  33. evt.clear()
  34. evt.wait()
  35. time.sleep(1)
  36. print('Mate_2 hugs Mate_1 for congratulation')
  37. if __name__ == '__main__':
  38. m = Mate_1()
  39. n = Mate_2()
  40. m.start()
  41. n.start()

上面的代码中,首先建立事件类实例,派生两个球员子线程,球员1会在开始后1秒收到足球,控球1秒后通过evt.set()唤醒另一个线程,模拟将球传出的过程,随后清除标志进入等待。此时球员2原本处于wait状态,收到set将内置Flag重置的信号后,被唤醒,表明自己接到球,控球1秒后再set传回,自己进入等待,球员1接到信号再次接球并得分后通知球员2,球员2则在收到进球信号后表示庆祝。

运行得到结果

  1. Mate_1 is running
  2. Mate_2 is running
  3. Mate_1 got the ball
  4. Mate_1 pass the ball to others
  5. Mate_2 got the ball
  6. Mate_2 pass the ball to others
  7. Mate_1 got the ball again
  8. Mate_1 makes a goal
  9. Mate_2 hugs Mate_1 for congratulation

结果中可以看出,以 set 方法模拟传球,使两个线程间互相配合执行。

函数延迟启动

利用Timer类可以实现对函数的延时启动,以及在未启动前取消启动的操作

  1. from threading import Timer
  2. from time import ctime, sleep
  3.  
  4. def func():
  5. print('Current time is', ctime(), 'Hello world')
  6.  
  7. timex = Timer(3, func)
  8. print('Current time is', ctime())
  9. timex.start()
  10. timex.join()
  11.  
  12. timex = Timer(3, func)
  13. print('Current time is', ctime())
  14. timex.start()
  15. sleep(1)
  16. timex.cancel()
  17. print('End')

运行得到结果

  1. Current time is Thu Aug 3 16:22:19 2017
  2. Current time is Thu Aug 3 16:22:22 2017 Hello world
  3. Current time is Thu Aug 3 16:22:22 2017
  4. End

从结果中可以看出,延时启动3秒起到了作用,而第二次的函数则在调用前被取消了

6 设置线程障碍

对于多线程来说,可以利用Barrier类实现对指定数量的线程进行阻碍,直到线程数量达到指定值后,同时释放所有的线程。

  1. from threading import Thread, Barrier
  2. import random
  3. import time
  4. COUNT = 0
  5.  
  6. def action():
  7. print('ACTION')
  8.  
  9. def foo():
  10. global COUNT
  11. time.sleep(random.randint(1, 7))
  12. print('Barrier_%d waiting' % COUNT)
  13. COUNT += 1
  14. barr.wait()
  15. print('Hello, world', COUNT)
  16.  
  17. barr = Barrier(7, action=action, timeout=20)
  18.  
  19. threads = []
  20. for i in range(7):
  21. threads.append(Thread(target=foo))
  22. for t in threads:
  23. t.start()
  24. for t in threads:
  25. t.join()
  26.  
  27. print('Pass barrier')

第 1-15 行,导入所需模块后,定义一个执行函数,用于越过障碍时执行,再定义一个线程函数,用于线程执行。线程执行函数会在进入后对全局变量+1,随后进入阻塞状态。

第 16 行,设置障碍上限,且线程数量与障碍上限相同。若此处线程数量大于障碍数,则跨过障碍后,多出的两个障碍会一直阻塞。可考虑使用reset函数重置计数,达到循环。

运行得到结果

  1. Barrier_0 waiting
  2. Barrier_1 waiting
  3. Barrier_2 waiting
  4. Barrier_3 waiting
  5. Barrier_4 waiting
  6. Barrier_5 waiting
  7. Barrier_6 waiting
  8. ACTION
  9. Hello, world 7
  10. Hello, world 7
  11. Hello, world 7
  12. Hello, world 7
  13. Hello, world 7
  14. Hello, world 7
  15. Hello, world 7
  16. Pass barrier

从输出的结果可以看到,当障碍数量达到上限的时候,会运行执行函数,随后唤醒所有的线程。

相关阅读


1. 基本概念

2. threading 模块

3. 多线程的建立

4. 锁与信号量

参考链接


《Python 核心编程 第3版》

Python的并发并行[1] -> 线程[3] -> 多线程的同步控制的更多相关文章

  1. Python的并发并行[1] -> 线程[1] -> 多线程的建立与使用

    多线程的建立与使用 目录 生成线程的三种方法 单线程与多线程对比 守护线程的设置 1 生成线程的三种方法 三种方式分别为: 创建一个Thread实例,传给它一个函数 创建一个Thread实例,传给它一 ...

  2. Python的并发并行[1] -> 线程[2] -> 锁与信号量

    锁与信号量 目录 添加线程锁 锁的本质 互斥锁与可重入锁 死锁的产生 锁的上下文管理 信号量与有界信号量 1 添加线程锁 由于多线程对资源的抢占顺序不同,可能会产生冲突,通过添加线程锁来对共有资源进行 ...

  3. Python的并发并行[1] -> 线程[0] -> threading 模块

    threading模块 / threading Module 1 常量 / Constants Pass 2 函数 / Function 2.1 setprofile()函数 函数调用: thread ...

  4. 并发 并行 进程 线程 协程 异步I/O python async

    一些草率不精确的观点: 并发: 一起发生,occurence: sth that happens. 并行: 同时处理. parallel lines: 平行线.thread.join()之前是啥?落霞 ...

  5. Python 之并发编程之线程下

    七.线程局部变量 多线程之间使用threading.local 对象用来存储数据,而其他线程不可见 实现多线程之间的数据隔离 本质上就是不同的线程使用这个对象时,为其创建一个只属于当前线程的字典 拿空 ...

  6. Python的并发并行[4] -> 并发[0] -> 利用线程池启动线程

    利用线程池启动线程 submit与map启动线程 利用两种方式分别启动线程,同时利用with上下文管理来对线程池进行控制 from concurrent.futures import ThreadPo ...

  7. Python 之并发编程之线程上

    一.线程概念 进程是资源分配的最小单位 线程是计算机中调度的最小单位 多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都 ...

  8. Python的并发并行[0] -> 基本概念

    基本概念 / Basic Concept  快速跳转 进程 / Process 线程 / Thread 协程 / Coroutine 全局解释器锁 / Global Interpreter Lock ...

  9. [并发并行]_[线程模型]_[Pthread线程使用模型之三 客户端/服务端模型(Client/Server]

    Pthread线程使用模型之三 客户端/服务端模型(Client/Server) 场景 1.在客户端/服务端模型时,客户端向服务端请求一些数据集的操作. 服务端执行执行操作独立的(多进程或跨网络)– ...

随机推荐

  1. DOS程序员手册(九)

    第14章参考手册概述     本书余下的章节将向读者们介绍BIOS.DOS各种各样API函数和服务,作为一名程 序员,了解和掌握这些知识是很有好处的.在所介绍的参考手册中,每部手册都汇集了大 量的资源 ...

  2. 【Linear Models for Binary Classification】林轩田机器学习基石

    首先回顾了几个Linear Model的共性:都是算出来一个score,然后做某种变化处理. 既然Linear Model有各种好处(训练时间,公式简单),那如何把Linear Regression给 ...

  3. 遍历两个自定义列表来实现字典(key:value)

    #自定义key    ${keys}    create list    key1    key2    key3 #自定义value    ${values}    create list    v ...

  4. 浅谈 css 之 position用法

    在 css中, position 属性有四个值可用: static(默认值).absolute.relative.fixed. relative:相对定位(相对于自身进行在常规流中的位置进行定位,保留 ...

  5. GLIBCXX3.4.21 not find

    在执行世界杯的二进制代码和安装keepaway中会遇到GLIBCXX3.4.21 not find的问题,其解决办法就是升级安装GCC. 一.首先查看当前gcc版本 $ strings /usr/li ...

  6. shell之iptables

    这五个位置也被称为五个钩子函数(hook functions),也叫五个规则链. 1.PREROUTING (路由前) 2.INPUT (数据包流入口) 3.FORWARD (转发管卡) 4.OUTP ...

  7. HDU 4472 Count (DP)

    题目:问n个节点构成完全对称的树有多少种方法. 因为树是完全对称的,所以它的子树也是完全对称的. 对于每个树,拿出一个根节点,枚举剩下的节点能拆分成多少个子树. #include <cstdio ...

  8. 软工实践Alpha冲刺(9/10)

    v队名:起床一起肝活队 组长博客:博客链接 作业博客:班级博客本次作业的链接 组员情况 组员1(队长):白晨曦 过去两天完成了哪些任务 描述: 已经解决登录注册等基本功能的界面. 完成非功能的主界面制 ...

  9. ajax的多次请求问题

    我们在用ajax请求数据时,可能会遇到一次点击多次触发的可能.(比如说:ajax 的 onreadystatechange 事件就会触发多次:这是因为 onreadystatechange 是一个事件 ...

  10. SQL查询oracle的nclob字段

    使用CONTAINS关键字查询NCLOB字段 SELECT  FORMATTED_MESSAGE    FROM     TBL_LOG WHERE     CONTAINS(FORMATTED_ME ...