[Python 多线程] Lock、阻塞锁、非阻塞锁 (八)
线程同步技术:
解决多个线程争抢同一个资源的情况,线程协作工作。一份数据同一时刻只能有一个线程处理。
解决线程同步的几种方法:
Lock、RLock、Condition、Barrier、semaphore
1)Lock 锁
锁,一旦线程获得锁,其它试图获取锁的线程将被阻塞。
当用阻塞参数设置为 False 时, 不要阻止。如果将阻塞设置为 True 的调用将阻止, 则立即返回 False;否则, 将锁定设置为锁定并返回 True。
Lock的方法:
acquire(blocking=True,timeout=-1) 加锁。默认True阻塞,阻塞可以设置超时时间。非阻塞时成功获取锁返回True,否则返回False。
当blocking设置为False时,不阻塞,同一个锁对象,其它线程可以重用,但最后都必须释放。
如果设置为True(默认True),其它试图调用锁的线程将阻塞,并立即返回False。阻塞可以设置超时时间。
release() 释放锁。可以从任何线程调用释放。已上锁的锁,会被重置为unlocked,对未上锁的锁调用,会抛RuntimeError异常: cannot release un-acquired lock。
不使用Lock的例子:
#不使用Lock锁的例子
import logging
import threading,time
logging.basicConfig(level=logging.INFO) # 10 -> 100cups
cups = []
lock = threading.Lock() def worker(lock:threading.Lock,task=100):
while True:
count = len(cups)
time.sleep(0.1) if count >= task:
break logging.info(count)
cups.append(1)
logging.info("{} make 1........ ".format(threading.current_thread().name))
logging.info("{} ending=======".format(len(cups))) for x in range(10):
threading.Thread(target=worker,args=(lock,100)).start() 运行结果:
INFO:root:Thread-7 make 1........
INFO:root:93
INFO:root:Thread-5 make 1........
INFO:root:95
INFO:root:Thread-6 make 1........
INFO:root:92
INFO:root:Thread-2 make 1........
INFO:root:94
INFO:root:Thread-8 make 1........
INFO:root:97
INFO:root:Thread-10 make 1........
INFO:root:96
INFO:root:Thread-4 make 1........
INFO:root:98
INFO:root:Thread-1 make 1........
INFO:root:99
INFO:root:Thread-9 make 1........
INFO:root:109 ending=======
INFO:root:109 ending=======
INFO:root:109 ending=======
还是使用前面的10个工人生产100杯子的例子, 当做到99个杯子时,10个工人都发现还少一个,都去做了一个,一共做了109个,超出了100个,就发生了不可预期的结果。
临界线判断失误,多生产了杯子。
解决方法就可以用锁,来解决资源争抢。当一个人看杯子数量时,就上锁,其它人只能等着,看完杯子后发现少一个就把这最后一个做出来,然后数量加一,解锁,其他人再看到已经有100个杯子时,就可以停止工作。
加锁的时机非常重要:看杯子数量时加锁,增加数量后释放锁。
使用Lock的例子:
#Lock
import logging
import threading
import time
logging.basicConfig(level=logging.INFO) # 10 -> 100cups
cups = []
lock = threading.Lock() def worker(lock:threading.Lock,task=100):
while True:
if lock.acquire(False):
count = len(cups) time.sleep(0.1) if count >= task:
lock.release()
break
logging.info(count) cups.append(1)
lock.release()
logging.info("{} make 1........ ".format(threading.current_thread().name))
logging.info("{} ending=======".format(len(cups))) for x in range(10):
threading.Thread(target=worker,args=(lock,100)).start() 运行结果:
INFO:root:0
INFO:root:Thread-1 make 1........
INFO:root:1
INFO:root:Thread-5 make 1........
INFO:root:2
INFO:root:Thread-6 make 1........
....
INFO:root:Thread-3 make 1........
INFO:root:97
INFO:root:Thread-3 make 1........
INFO:root:98
INFO:root:Thread-4 make 1........
INFO:root:99
INFO:root:Thread-3 make 1........
INFO:root:100 ending=======
INFO:root:100 ending=======
INFO:root:100 ending=======
.....
在使用了锁以后,虽然保证了结果的准确性,但是性能下降了很多。
一般来说加锁以后还要有一些功能实现,在释放之前还有可能抛异常,一旦抛出异常,锁是无法释放,但是当前线程可能因为这个异常被终止了,这就产生了死锁。
死锁解决办法:
1、使用 try..except..finally 语句处理异常、保证锁的释放
2、with 语句上下文管理,锁对象支持上下文管理。只要实现了__enter__和__exit__魔术方法的对象都支持上下文管理。
锁的应用场景:
独占锁: 锁适用于访问和修改同一个共享资源的时候,即读写同一个资源的时候。
共享锁: 如果共享资源是不可变的值时,所有线程每一次读取它都是同一样的值,这样的情况就不需要锁。
使用锁的注意事项:
- 少用锁,必要时用锁。使用了锁,多线程访问被锁的资源时,就变成了串行,要么排队执行,要么争抢执行。
- 加锁时间越短越好,不需要就立即释放锁。
- 一定要避免死锁。
不使用锁时,有了效率,但是结果是错的。
使用了锁,变成了串行,效率地下,但是结果是对的。
import threading
import time lock = threading.Lock() def work():
print('working..')
time.sleep(0.2)
lock.release() # 1解锁 lock.acquire() # 1上锁
print("get locker 1") threading.Thread(target=work).start() time.sleep(1)
lock.acquire() # 2上锁 print("get locker 2")
threading.Thread(target=work).start() print("release locker") 运行结果:
get locker 1
working..
get locker 2
working..
release locker
同一个锁对象在释放后可以再次使用。
但是如果同一把锁加锁后,又被别人拿了,自己就阻塞了:
import threading
import time lock = threading.Lock() def work():
print('working..')
time.sleep(0.2)
lock.release() # 1解锁 lock.acquire() # 1上锁
print("get locker 1")
lock.acquire() # 2上锁
print("get locker 2") threading.Thread(target=work).start()
threading.Thread(target=work).start() print("release locker") 运行结果:
get locker 1
阻塞状态....
阻塞锁:
#阻塞锁
import threading,time lock = threading.Lock() def foo():
ret = lock.acquire()
print("{} Locked. {}".format(ret,threading.current_thread()))
time.sleep(10) threading.Thread(target=foo).start()
threading.Thread(target=foo).start() 运行结果:
True Locked. <Thread(Thread-1, started 123145559191552)>
lock.acquire()默认设置blocking=True,两个线程使用同一个Lock锁对象,只要Thread-1线程不释放,第二个线程就无法获取锁,且会使Thread-1线程阻塞。
如果想让多个线程同时都可以使用一个锁对象,就必须使用非阻塞锁,或者第一个线程使用完锁之后立刻释放,然后第二个线程再使用。
非阻塞锁:
#非阻塞锁
import threading,time lock = threading.Lock() def foo():
ret = lock.acquire(False)
print("{} Locked. {}".format(ret,threading.current_thread()))
time.sleep(10) threading.Thread(target=foo).start()
threading.Thread(target=foo).start() 运行结果:
True Locked. <Thread(Thread-1, started 123145516146688)>
False Locked. <Thread(Thread-2, started 123145521401856)> Process finished with exit code 0
lock.acquire(False)设置blocking=False表示不阻塞,使用同一个Lock锁对象时,第二个线程仍可以使用锁,且第一个锁不会被阻塞。
非阻塞锁2:
#非阻塞锁
import threading,logging,time FORMAT = '%(asctime)s\t [%(threadName)s,%(thread)d] %(message)s'
logging.basicConfig(level=logging.INFO,format=FORMAT) def worker(tasks):
for task in tasks:
time.sleep(0.01)
if task.lock.acquire(False): #False非阻塞
logging.info('{} {} begin to start'.format(threading.current_thread().name,task.name))
else:
logging.info('{} {} is working'.format(threading.current_thread().name,task.name)) class Task:
def __init__(self,name):
self.name = name
self.lock = threading.Lock() tasks = [Task('task={}'.format(t)) for t in range(5)] for i in range(3):
t = threading.Thread(target=worker,name='worker-{}'.format(i),args=(tasks,))
t.start() 运行结果:
2017-12-19 16:37:49,556 [worker-2,123145390018560] worker-2 task=0 begin to start
2017-12-19 16:37:49,556 [worker-1,123145384763392] worker-1 task=0 is working
2017-12-19 16:37:49,557 [worker-0,123145379508224] worker-0 task=0 is working
2017-12-19 16:37:49,567 [worker-2,123145390018560] worker-2 task=1 begin to start
2017-12-19 16:37:49,567 [worker-1,123145384763392] worker-1 task=1 is working
2017-12-19 16:37:49,568 [worker-0,123145379508224] worker-0 task=1 is working
2017-12-19 16:37:49,580 [worker-1,123145384763392] worker-1 task=2 begin to start
2017-12-19 16:37:49,580 [worker-2,123145390018560] worker-2 task=2 is working
2017-12-19 16:37:49,580 [worker-0,123145379508224] worker-0 task=2 is working
2017-12-19 16:37:49,591 [worker-1,123145384763392] worker-1 task=3 begin to start
2017-12-19 16:37:49,592 [worker-2,123145390018560] worker-2 task=3 is working
2017-12-19 16:37:49,592 [worker-0,123145379508224] worker-0 task=3 is working
2017-12-19 16:37:49,604 [worker-1,123145384763392] worker-1 task=4 begin to start
2017-12-19 16:37:49,604 [worker-2,123145390018560] worker-2 task=4 is working
2017-12-19 16:37:49,604 [worker-0,123145379508224] worker-0 task=4 is working
[Python 多线程] Lock、阻塞锁、非阻塞锁 (八)的更多相关文章
- GIL全局解释器锁,线程池与进程池 同步异步,阻塞与非阻塞,异步回调
GIL全局解释器锁 1.什么是GIL 官方解释:'''In CPython, the global interpreter lock, or GIL, is a mutex that prevents ...
- python并发编程(并发与并行,同步和异步,阻塞与非阻塞)
最近在学python的网络编程,学了socket通信,并利用socket实现了一个具有用户验证功能,可以上传下载文件.可以实现命令行功能,创建和删除文件夹,可以实现的断点续传等功能的FTP服务器.但在 ...
- 4月27日 python学习总结 GIL、进程池、线程池、同步、异步、阻塞、非阻塞
一.GIL:全局解释器锁 1 .GIL:全局解释器锁 GIL本质就是一把互斥锁,是夹在解释器身上的, 同一个进程内的所有线程都需要先抢到GIL锁,才能执行解释器代码 2.GIL的优缺点: 优点: 保 ...
- 【python】-- 事件驱动介绍、阻塞IO, 非阻塞IO, 同步IO,异步IO介绍
事件驱动介绍 一.前言 通常,我们写服务器处理模型的程序时,有以下几种模型: (1)每收到一个请求,创建一个新的进程,来处理该请求: (2)每收到一个请求,创建一个新的线程,来处理该请求: (3)每收 ...
- python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)
昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...
- python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)
python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程 并行与并发 同步与异步 阻塞与非阻塞 CPU密集型与IO密集型 线程与进程 进 ...
- Python web框架 Tornado异步非阻塞
Python web框架 Tornado异步非阻塞 异步非阻塞 阻塞式:(适用于所有框架,Django,Flask,Tornado,Bottle) 一个请求到来未处理完成,后续一直等待 解决方案: ...
- python学习笔记-(十四)I/O多路复用 阻塞、非阻塞、同步、异步
1. 概念说明 1.1 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程序,可 ...
- Python网络编程-IO阻塞与非阻塞及多路复用
前言 问题:普通套接字实现的服务端的缺陷 一次只能服务一个客户端! accept阻塞! 在没有新的套接字来之前,不能处理已经建立连接的套接字的请求 re ...
随机推荐
- android 使用图片轮播图---banner 使用
转自:https://github.com/youth5201314/banner 使用步骤 Step 1.依赖banner Gradle dependencies{ compile 'com.you ...
- InfluxDB 的卸载与重装
我是通过下面方式安装的,所以卸载也是用的 rpm 的卸载命令 wget http://s3.amazonaws.com/influxdb/influxdb-latest-1.x86_64.rpm su ...
- mysql数据导入mongoDB
目前许多平台都会同时使用MySQL , mongoDB 两款数据库软件,他们之间的数据同步交换也是经常面临的问题,如何定时的进行数据交换同步是一个要面对的问题. 通过Treesoft数据库管理系统可以 ...
- linux ubuntu 安装nginx
参考原文 在Ubuntu下安装Nginx有以下方法,但是如果想要安装最新版本的就必须下载源码包编译安装. 一.基于APT源安装 sudo apt-get install nginx 安装好的文件位置: ...
- html和css入门 (一)
HTML简介 什么是HTML HTML 的全称为 超文本标记语言(Hyper Text Markup Language),这种语言给我们提供一种建立结构性文档的方法.通过表示结构性的标签语法,我们可以 ...
- <Android 应用 之路> MPAndroidChart~ScatterChart
简介 MPAndroidChart是PhilJay大神给Android开发者带来的福利.MPAndroidChart是一个功能强大并且使用灵活的图表开源库,支持Android和IOS两种,这里我们暂时 ...
- 如何正确地在SOE中输出日志信息
ArcGIS for Server提供完善的日志管理机制,用于日志的记录.查询和自动清除.开发人员在开发编写SOE代码时,应该采用该机制进行日志记录的输出.如果不采用该机制,输出的日志消息会写到Arc ...
- CentOS6.5(2)----安装Tab键自动补全功能:bash-completion
首先要确保网络畅通,因为该过程要通过网络下载相关的软件包. 在 root 用户下,使用 cd ~/Downloads 命令进入下载文件夹,然后依次输入如下三个命令: [root@prime:~/Doc ...
- CentOS6.5(1)----设置静态IP并禁用IPV6
使用vim命令编辑 /etc/sysconfig/network-scripts/ifcfg-eth0 文件 vim /etc/sysconfig/network-scripts/ifcfg-eth0 ...
- django开发博客(1) 入门
现在正式开始博客开发 1.安装django1.4 如果你使用的是fedoraDVD版,安装时选择了web开发组建,这一步可以省略,因为它自带django环境 django下载地址 https://ww ...