上一篇随笔我们学了全局解释器锁,前面也学了互斥锁,今天学习一些与锁相关的点,例如递归锁,信号量,Event,还会学习我们已经很熟悉的队列,不过这次的队列是作为一个模块出现的。

本篇导航:

一、同步锁

1、join与互斥锁

线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来

join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

2、GIL VS Lock

锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据。结论:保护不同的数据就应该加不同的锁。

GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock

分析:

1)100个线程去抢GIL锁,即抢执行权限
2) 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
3)极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
4)直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程

3、join与互斥锁对比实例

1)未处理代码:

#不加锁:并发执行,速度快,数据不安全
from threading import currentThread,Thread
import time
def task():
time.sleep(1)
global n
print('%s is running' %currentThread().getName())
temp = n
time.sleep(0.1)
n = temp - 1 if __name__ == '__main__':
n = 100
t_l = []
s1 = time.time()
for i in range(100):
t=Thread(target=task)
t_l.append(t)
t.start()
for t in t_l:
t.join() s2 = time.time()
print('主:%s n:%s' %(s2-s1,n)) '''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:1.1128411293029785 n:99
'''

初始

2)加互斥锁:

#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import currentThread,Thread,Lock
import time
def task():
#未加锁的代码并发运行
time.sleep(1)
print('%s start to run' %currentThread().getName())
global n
#加锁的代码串行运行
mutex.acquire()
temp = n
time.sleep(0.1)
n = temp - 1
mutex.release() if __name__ == '__main__':
n = 100
mutex = Lock()
t_l = []
s1 = time.time()
for i in range(100) :
t = Thread(target=task)
t_l.append(t)
t.start()
for t in t_l :
t.join()
s2 = time.time()
print('主:%s n:%s' %(s2-s1,n)) '''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:11.091605186462402 n:0
'''

Lock

3)join效果

from threading import currentThread,Thread
import time
def task():
time.sleep(1)
print('%s start to run' %currentThread().getName())
global n
temp = n
time.sleep(0.1)
n = temp - 1 if __name__ == '__main__':
n = 100
s1 = time.time()
for i in range(100):
t = Thread(target=task)
t.start()
t.join()
s2 = time.time()
print('主:%s n:%s' %(s2-s1,n)) '''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:110.16416668891907 n:0
'''

join

即在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.


二、死锁现象与递归锁

1、死锁现象

进程也有死锁与递归锁与线程中相同

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

from threading import Lock,Thread
import time
mutexA=Lock()
mutexB=Lock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2() def f1(self):
mutexA.acquire()
print('\033[31m%s 拿到A锁' %self.name)
mutexB.acquire()
print('\033[32m%s 拿到B锁' %self.name)
mutexB.release()
mutexA.release() def f2(self):
mutexB.acquire()
print('\033[33m%s 拿到B锁' %self.name)
time.sleep(1)
mutexA.acquire()
print('\033[34m%s 拿到A锁' %self.name)
mutexA.release()
mutexB.release() if __name__ == '__main__':
for i in range(10):
t=MyThread()
t.start()

死锁状态,程序永远无法结束:

2、递归锁

上述情况可以用递归锁解决

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

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

from threading import ,Thread,RLock
import time
mutexB=mutexA=RLock()
#一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
class MyThread(Thread):
def run(self):
self.f1()
self.f2() def f1(self):
mutexA.acquire()
print('\033[31m%s 拿到A锁' %self.name)
mutexB.acquire()
print('\033[32m%s 拿到B锁' %self.name)
mutexB.release()
mutexA.release() def f2(self):
mutexB.acquire()
print('\033[33m%s 拿到B锁' %self.name)
time.sleep(1)
mutexA.acquire()
print('\033[34m%s 拿到A锁' %self.name)
mutexA.release()
mutexB.release() if __name__ == '__main__':
for i in range(10):
t=MyThread()
t.start()

三、信号量Semaphore

Semaphore也是一种锁不过这个锁可以自己定义同时可以进入锁的线程数

Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1;调用release() 时内置计数器+1;

计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

实例:

1、互斥锁Lock就像家里的厕所每次只能进一人,进去后锁门其他人在外面等着(这是学进程互斥锁时的例子)

from multiprocessing import Process,Lock,current_process
import time,random
def work(mutex):
mutex.acquire() #上锁
print('%s 上厕所' %current_process().name)
time.sleep(random.randint(1,3))
print('%s 走了' %current_process().name)
mutex.release() #开锁 if __name__ == '__main__':
mutex=Lock() #实例化(互斥锁)
print('start...')
for i in range(20):
t=Process(target=work,args=(mutex,))
t.start()

2、信号量Semaphore就像是街道的公共厕所有固定个数的隔间(例如5个),刚开始可以进去5个,然后出来几个便可以再进去几个

from threading import Thread,Semaphore,currentThread
import time,random
sm=Semaphore(5)
def task():
sm.acquire()
print('%s 上厕所' %currentThread().getName())
time.sleep(random.randint(1,3))
print('%s 走了' %currentThread().getName())
sm.release()
if __name__ == '__main__':
for i in range(20):
t=Thread(target=task)
t.start()

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


四、Event

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

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

1、模拟红绿灯

from threading import Thread,Event,currentThread
import time
e=Event() def traffic_lights():
time.sleep(5)
e.set() def car():
print('\033[41m%s 等' %currentThread().getName())
e.wait()
print('\033[42m%s 跑' %currentThread().getName()) if __name__ == '__main__':
for i in range(10):
t=Thread(target=car)
t.start()
traffic_thread=Thread(target=traffic_lights)
traffic_thread.start()

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

from threading import Thread,Event,currentThread
import time
e=Event()
def conn_mysql():
count=1
while not e.is_set():
if count > 3:
raise ConnectionError('尝试链接的次数过多')
print('\033[35m%s 第%s次尝试' %(currentThread().getName(),count))
e.wait(timeout=1)
count+=1
print('\033[32m%s 开始链接' %currentThread().getName()) def check_mysql():
print('\033[34m%s 检测mysql...' %currentThread().getName())
time.sleep(2)
e.set()
if __name__ == '__main__':
for i in range(3):
t=Thread(target=conn_mysql)
t.start()
t=Thread(target=check_mysql)
t.start()

五、定时器

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

from threading import Timer

def hello(n):
print("hello, world",n) #三秒后运行hello函数传入参数123
t = Timer(3, hello, args=(123,))
t.start()

六、线程queue

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

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

队列在线程编程中尤其有用,因为必须在多个线程之间安全地交换信息。

1、queue.Queue() 先进先出

import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third') print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''

2、queue.LifoQueue() 后进先出

import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third') print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''

3、queue.PriorityQueue() 存储数据时可设置优先级的队列

import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c')) print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

34、锁问题与线程queue的更多相关文章

  1. python开发线程:死锁和递归锁&信号量&定时器&线程queue&事件evevt

    一 死锁现象与递归锁 进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将 ...

  2. 锁问题与线程queue

    一.同步锁 1.join与互斥锁 线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限 ...

  3. python并发编程之线程(二):死锁和递归锁&信号量&定时器&线程queue&事件evevt

    一 死锁现象与递归锁 进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将 ...

  4. 死锁与递归锁 信号量 event 线程queue

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

  5. Python之网路编程之死锁,递归锁,信号量,Event事件,线程Queue

    一.死锁现象与递归锁 进程也是有死锁的 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去.此时称系统处于死锁状态或系统 ...

  6. 1.gil全局解释器锁, 2. 死锁与递归锁 3. 信号量 4. Event事件 5. 线程queue

    gil本质就是一把互斥锁,相当于执行权限,每个进程都会存在一把gil,同一进程内的多个线程必须抢到gil 之后才能使用cpython解释器来执行自己的代码,同一进程下的多线程不能并行,但可以实现并发 ...

  7. Python 36 死锁现象和递归锁、信号量、Event事件、线程queue

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

  8. GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发

    一.GIL全局解释锁 在Cpython解释器才有GIL的概念,不是python的特点 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势. 1.GIL介绍 ...

  9. 同步锁 死锁与递归锁 信号量 线程queue event事件

    二个需要注意的点: 1 线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock任然没有被释放则阻塞,即便是拿到执行权限GIL也要 ...

随机推荐

  1. MySQL5.7使用过程中遇到的问题

    Q1.MySQL无法启动服务,启动服务时提示:"本地计算机 上的 MySQL 服务启动后停止.某些服务在未由其他服务或程序使用时将自动停止." PS.解压缩的MySQL安装过程也可 ...

  2. 输出a-b之间的随机数并考虑异常

    输出a-b之间的随机数并考虑异常 代码如下: package Day05;import java.util.Scanner;import java.util.Random; public class ...

  3. swift 3.0 基础练习 面向对象 类

    模拟需求 创建100个女朋友 1.用面向对象思想 2.名字随机 3.年龄随机 4.拥有约会功能 5.将所有女朋友信息输出 class GirlFirend: NSObject { var name:S ...

  4. [js] webgl 初探 - 绘制三角形

    摘要: 1. webgl 概念挺多的, 顶点着色器.片段着色器, 坐标 2. 绘制前期准备工作好多 目前看的比较好的教材: https://developer.mozilla.org/zh-CN/do ...

  5. 【HTML】谈谈html的meta标签

    一.定义&用法 <meta> 元素可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词. <meta> 标签位于文档的头 ...

  6. Samba服务部署

    Samba,是种用来让UNIX系列的操作系统与微软Windows操作系统的SMB/CIFS(Server Message Block/Common Internet File System)网络协议做 ...

  7. POJ 2386 Lake Counting (简单深搜)

    Description Due to recent rains, water has pooled in various places in Farmer John's field, which is ...

  8. 八数码问题+路径寻找问题+bfs(隐式图的判重操作)

    Δ路径寻找问题可以归结为隐式图的遍历,它的任务是找到一条凑够初始状态到终止问题的最优路径, 而不是像回溯法那样找到一个符合某些要求的解. 八数码问题就是路径查找问题背景下的经典训练题目. 程序框架 p ...

  9. Jersey实现Restful服务

    jersey 是基于Java的一个轻量级RESTful风格的Web Services框架.以下我基于IDEA实现Restful完整Demo. 1.创建maven-web工程,后面就是正常的maven工 ...

  10. overlay 是如何隔离的?- 每天5分钟玩转 Docker 容器技术(53)

    不同的 overlay 网络是相互隔离的.我们创建第二个 overlay 网络 ov_net2 并运行容器 bbox3. bbox3 分配到的 IP 是 10.0.1.2,尝试 ping bbox1( ...