一、锁

线程为什么要有锁:

  += 、-= 赋值操作数据不安全(要经过取值、计算、放回值,3部操作)

  pop 、append 都是数据安全的(只有添加和删除,一次操作)

  队列也是数据安全的

1、同步锁

  1. import os, time
  2. from threading import Thread
  3.  
  4. def work():
  5. global n
  6. temp = n
  7. time.sleep(0.1)
  8. n = temp - 1
  9.  
  10. if __name__ == '__main__':
  11. n = 100
  12. l = []
  13. for i in range(100):
  14. p = Thread(target=work)
  15. p.start()
  16. l.append(p)
  17. for p in l:
  18. p.join()
  19. print(n) # 结果可能为99

多个线程抢占资源的情况

  1. import threading
  2. R=threading.Lock()
  3. R.acquire()
  4. '''
  5. 对公共数据的操作
  6. '''
  7. R.release()
  1. import os,time
  2. from threading import Thread,Lock
  3.  
  4. def work(lock):
  5. global n
  6. lock.acquire()
  7. temp = n
  8. time.sleep(0.1)
  9. n = temp - 1
  10. lock.release()
  11.  
  12. if __name__ == '__main__':
  13. lock = Lock()
  14. n = 100
  15. l = []
  16. for i in range(100):
  17. p = Thread(target=work, args=(lock,))
  18. p.start()
  19. l.append(p)
  20. for p in l:
  21. p.join()
  22. print(n) # 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

同步锁的引用

  1. # 不加锁:并发执行,速度快,数据不安全
  2.  
  3. from threading import current_thread,Thread,Lock
  4. import os,time
  5.  
  6. def task():
  7. global n
  8. print('%s is running' %current_thread().getName())
  9. temp=n
  10. time.sleep(0.5)
  11. n=temp-1
  12.  
  13. if __name__ == '__main__':
  14. n=100
  15. lock=Lock()
  16. threads=[]
  17. start_time=time.time()
  18. for i in range(100):
  19. t=Thread(target=task)
  20. threads.append(t)
  21. t.start()
  22. for t in threads:
  23. t.join()
  24.  
  25. stop_time=time.time()
  26. print('主:%s n:%s' %(stop_time-start_time,n))
  27.  
  28. '''
  29. Thread-1 is running
  30. Thread-2 is running
  31. ......
  32. Thread-100 is running
  33. 主:0.5216062068939209 n:99
  34. '''
  35.  
  36. # 不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
  37.  
  38. from threading import current_thread,Thread,Lock
  39. import os,time
  40.  
  41. def task():
  42. # 未加锁的代码并发运行
  43. time.sleep(3)
  44. print('%s start to run' %current_thread().getName())
  45. global n
  46. # 加锁的代码串行运行
  47. lock.acquire()
  48. temp=n
  49. time.sleep(0.5)
  50. n=temp-1
  51. lock.release()
  52.  
  53. if __name__ == '__main__':
  54. n=100
  55. lock=Lock()
  56. threads=[]
  57. start_time=time.time()
  58. for i in range(100):
  59. t=Thread(target=task)
  60. threads.append(t)
  61. t.start()
  62. for t in threads:
  63. t.join()
  64. stop_time=time.time()
  65. print('主:%s n:%s' %(stop_time-start_time,n))
  66.  
  67. '''
  68. Thread-1 is running
  69. Thread-2 is running
  70. ......
  71. Thread-100 is running
  72. 主:53.294203758239746 n:0
  73. '''
  74.  
  75. # 有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
  76. # 没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
  77. # start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
  78. # 单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
  79.  
  80. from threading import current_thread,Thread,Lock
  81. import os,time
  82.  
  83. def task():
  84. time.sleep(3)
  85. print('%s start to run' %current_thread().getName())
  86. global n
  87. temp=n
  88. time.sleep(0.5)
  89. n=temp-1
  90.  
  91. if __name__ == '__main__':
  92. n=100
  93. lock=Lock()
  94. start_time=time.time()
  95. for i in range(100):
  96. t=Thread(target=task)
  97. t.start()
  98. t.join()
  99. stop_time=time.time()
  100. print('主:%s n:%s' %(stop_time-start_time,n))
  101.  
  102. '''
  103. Thread-1 start to run
  104. Thread-2 start to run
  105. ......
  106. Thread-100 start to run
  107. 主:350.6937336921692 n:0 #耗时是多么的恐怖
  108. '''

互斥锁与join的区别

2、死锁与递归锁

  进程也有死锁与递归锁

  所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

  1. import time
  2. from threading import Lock
  3.  
  4. mutexA=Lock()
  5. mutexA.acquire()
  6. mutexA.acquire()
  7. print(123)
  8. mutexA.release()
  9. mutexA.release()

  解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

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

  1. import time
  2. from threading import RLock
  3.  
  4. mutexA=RLock()
  5. mutexA.acquire()
  6. mutexA.acquire()
  7. print(123)
  8. mutexA.release()
  9. mutexA.release()

  典型问题:科学家吃面

  1. import time
  2. from threading import Thread,Lock
  3.  
  4. noodle_lock = Lock()
  5. fork_lock = Lock()
  6. def eat1(name):
  7. noodle_lock.acquire()
  8. print('%s拿到面条了' % name)
  9. fork_lock.acquire()
  10. print('%s拿到叉子了' % name)
  11. print('%s吃面' % name)
  12. time.sleep(0.3)
  13. fork_lock.release()
  14. print('%s放下叉子' % name)
  15. noodle_lock.release()
  16. print('%s放下面' % name)
  17.  
  18. def eat2(name):
  19. fork_lock.acquire()
  20. print('%s拿到叉子了' % name)
  21. noodle_lock.acquire()
  22. print('%s拿到面条了' % name)
  23. print('%s吃面' % name)
  24. time.sleep(0.3)
  25. noodle_lock.release()
  26. print('%s放下面' % name)
  27. fork_lock.release()
  28. print('%s放下叉子' % name)
  29.  
  30. if __name__ == '__main__':
  31. name_list1 = ['alex', 'wusir']
  32. name_list2 = ['nezha', 'yuan']
  33. for name in name_list1:
  34. Thread(target=eat1, args=(name,)).start()
  35. for name in name_list2:
  36. Thread(target=eat2, args=(name,)).start()

死锁现象

  1. import time
  2. from threading import Thread,RLock
  3.  
  4. fork_lock = noodle_lock = RLock()
  5.  
  6. def eat1(name):
  7. noodle_lock.acquire()
  8. print('%s拿到面条了' % name)
  9. fork_lock.acquire()
  10. print('%s拿到叉子了' % name)
  11. print('%s吃面' % name)
  12. time.sleep(0.3)
  13. fork_lock.release()
  14. print('%s放下叉子' % name)
  15. noodle_lock.release()
  16. print('%s放下面' % name)
  17.  
  18. def eat2(name):
  19. fork_lock.acquire()
  20. print('%s拿到叉子了' % name)
  21. noodle_lock.acquire()
  22. print('%s拿到面条了' % name)
  23. print('%s吃面' % name)
  24. time.sleep(0.3)
  25. noodle_lock.release()
  26. print('%s放下面' % name)
  27. fork_lock.release()
  28. print('%s放下叉子' % name)
  29.  
  30. if __name__ == '__main__':
  31. name_list1 = ['alex', 'wusir']
  32. name_list2 = ['nezha', 'yuan']
  33. for name in name_list1:
  34. Thread(target=eat1, args=(name,)).start()
  35. for name in name_list2:
  36. Thread(target=eat2, args=(name,)).start()

递归锁解决死锁问题

二、信号量

同进程的一样:

  Semaphore管理一个内置的计数器,
  每当调用acquire()时内置计数器-1;
  调用release() 时内置计数器+1;
  计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

信号量与池的区别:

  是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

  1. import time
  2. from threading import Semaphore,Thread
  3.  
  4. def func(index, sem):
  5. sem.acquire()
  6. print(index)
  7. time.sleep(1)
  8. sem.release()
  9.  
  10. if __name__ == '__main__':
  11. sem = Semaphore(5)
  12. for i in range(10):
  13. Thread(target=func, args=(i, sem)).start()

三、事件

同进程的一样:

  线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

  1. event.isSet():返回event的状态值;
  2. event.wait():如果 event.isSet()==False将阻塞线程;
  3. event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
  4. event.clear():恢复event的状态值为False

  例如,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作

  1. import random
  2. import time
  3. from threading import Event, Thread
  4.  
  5. def check(e):
  6. print('开始检测数据库连接')
  7. time.sleep(random.randint(1, 5)) # 检测数据库连接
  8. e.set() # 成功了
  9.  
  10. def connect(e):
  11. for i in range(3):
  12. e.wait(0.5)
  13. if e.is_set():
  14. print('数据库连接成功')
  15. break
  16. else:
  17. print('尝试连接数据库%s次失败' % (i+1))
  18. else:
  19. raise TimeoutError # 3次都不成功则主动抛异常
  20.  
  21. e = Event()
  22. Thread(target=connect, args=(e,)).start()
  23. Thread(target=check, args=(e,)).start()

实例

四、条件

使得线程等待,只有满足某条件时,才释放n个线程

  1. Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquirerelease方法外,还提供了waitnotify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

详细说明

代码说明:

  1. from threading import Condition, Thread
  2.  
  3. def func(con, index):
  4. print('%s在等待' % index)
  5. con.acquire()
  6. con.wait()
  7. print('%s do something' % index)
  8. con.release()
  9.  
  10. con = Condition()
  11. for i in range(10):
  12. Thread(target=func, args=(con, i)).start()
  13. # con.acquire()
  14. # con.notify_all()
  15. # con.release()
  16. count = 10
  17. while count > 0:
  18. num = int(input('>>>'))
  19. con.acquire()
  20. con.notify(num)
  21. count -= num
  22. con.release()

五、定时器

定时器,指定n秒后执行某个操作

  1. from threading import Timer
  2.  
  3. def func():
  4. print('执行我啦')
  5.  
  6. Timer(5, func).start()
  7. print('主线程')

六、队列

queue队列 :使用import queue,用法与进程Queue一样

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

class queue.Queue(maxsize=0)   # 先进先出
  1. import queue
  2.  
  3. q=queue.Queue()
  4. q.put('first')
  5. q.put('second')
  6. q.put('third')
  7.  
  8. print(q.get())
  9. print(q.get())
  10. print(q.get())
  11. '''
  12. 结果(先进先出):
  13. first
  14. second
  15. third
  16. '''

class queue.LifoQueue(maxsize=0)   # 后进先出

  1. import queue
  2.  
  3. q=queue.LifoQueue()
  4. q.put('first')
  5. q.put('second')
  6. q.put('third')
  7.  
  8. print(q.get())
  9. print(q.get())
  10. print(q.get())
  11. '''
  12. 结果(后进先出):
  13. third
  14. second
  15. first
  16. '''

class queue.PriorityQueue(maxsize=0)   # 存储数据时可设置优先级的队列

  1. import queue
  2.  
  3. q=queue.PriorityQueue()
  4. # put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
  5. q.put((20,'a'))
  6. q.put((10,'b'))
  7. q.put((30,'c'))
  8.  
  9. print(q.get())
  10. print(q.get())
  11. print(q.get())
  12. '''
  13. 结果(数字越小优先级越高,优先级高的优先出队):
  14. (10, 'b')
  15. (20, 'a')
  16. (30, 'c')
  17. '''

《Python》线程之锁、信号量、事件、条件、定时器、队列的更多相关文章

  1. Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures

    参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...

  2. python 全栈开发,Day42(Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures)

    昨日内容回顾 线程什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线程是什么关系? 线程是在进程中的 一个执行单位 多进程 本质上开启的这个进程里就有一个线程 多线程 单纯的在当 ...

  3. python 并发编程 锁 / 信号量 / 事件 / 队列(进程间通信(IPC)) /生产者消费者模式

    (1)锁:进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 虽然使用加锁的形式实现了 ...

  4. 《python》join、守护进程、锁/信号量/事件、进程队列

    一.multiprocess.process模块 1.join方法 阻塞主进程,等待子进程执行完毕再放开阻塞 import time import random from multiprocessin ...

  5. 进程同步控制(锁,信号量,事件), 进程通讯(队列和管道,生产者消费者模型) 数据共享(进程池和mutiprocess.Pool模块)

    参考博客 https://www.cnblogs.com/xiao987334176/p/9025072.html#autoid-1-1-0 进程同步(multiprocess.Lock.Semaph ...

  6. python 线程之 threading(四)

    python 线程之 threading(三) http://www.cnblogs.com/someoneHan/p/6213100.html中对Event做了简单的介绍. 但是如果线程打算一遍一遍 ...

  7. python 线程之 threading(三)

    python 线程之 threading(一)http://www.cnblogs.com/someoneHan/p/6204640.html python 线程之 threading(二)http: ...

  8. 铁乐学python_Day42_线程-信号量事件条件

    铁乐学python_Day42_线程-信号量事件条件 线程中的信号量 同进程的一样,Semaphore管理一个内置的计数器, 每当调用acquire()时内置计数器-1:调用release() 时内置 ...

  9. python 线程之_thread

    python 线程之_thread _thread module: 基本用法: def child(tid): print("hello from child",tid) _thr ...

  10. ucos中信号量 事件标志 消息队列都怎么用

    信号量 事件标志和消息队列分别应用于什么场景(反正我学的时候有点闹不清,现在总结一下): 信号量和事件标志用于任务同步.详细来说,这个功能可以替代以前裸机中你打一个标记的功能,比如使用了一个定时器,5 ...

随机推荐

  1. css动效库animate.css和swiper.js

    animate.css https://daneden.github.io/animate.css/ 学习的文档:http://www.jq22.com/jquery-info819 腾讯团队的JXa ...

  2. word空白页怎么删除

    最简单的,直接按键盘上的BackSpace或者Delete键,来进行删除.  分页符过到.打开“编辑”-->替换-->高级-->特殊字符-->手工分页符-->“全部替换” ...

  3. linux下安装nginx以及常用命令指南

    安装nginx之前,要先在服务器上安装nginx运行所需要的依赖包 目录选择:一般选择 "/usr/local/" 1.安装PCRE库 离线安装包:https://pan.baid ...

  4. 荧光激活细胞分选( FACS)

    全称:fluorescence-activated cell sorting 参考: 利用荧光激活细胞分选技术获取荧光蛋白标记肾小球足细胞 荧光激活细胞分离技术在角膜缘干细胞研究中的应用 [求助]急! ...

  5. week02 课堂作业

    测试一:(点此看原题目) 运行结果: 测试二:(点此看原题目) 运行结果: 测试三:(点此看原题目) 运行结果:

  6. English trip V1 - B 18. Workplaces 工作地方 Teacher:Russell Key: do / does

    In this lesson you will learn to talk about workplaces. 课上内容(Lesson) My English Teacher name is Russ ...

  7. JavaScript的几个概念简单理解(深入解释见You Don't know JavaScript这本书)

    ES201X是JavaScript的一个版本. ES2015新的feature let, const Scope, 块作用域 Hoisting Closures DataStructures: Obj ...

  8. 从早期 Spring Boot 版本升级

    如果你现在正在从早期的 Spring Boot 版本进行升级的话,请访问 “migration guide” on the project wiki 页面,这个页面提供了有关升级的详细指南.同时也请查 ...

  9. CentOS7 添加开机启动项

     centos6 加入开机启动:   vim /etc/rc.d/rc.local 注意命令不要出错,重启后生效   或者   centos 7 下: vim /lib/systemd/system/ ...

  10. acl使用示例

    declare   v_count  number;  uprinciple varchar2(20);  principle  varchar2(20);  begin uprinciple := ...