• 互斥锁
  • 死锁和递归锁
  • Semaphore信号量
  • Event事件

互斥锁

互斥锁也叫用户锁、同步锁。
在多进程/多线程程序中,当多个线程处理一个公共数据时,会有数据安全问题,唯一能保证数据安全的,就是通过加锁的方式,同一时间只能有一个修改数据的操作,将处理数据变为串行。虽然牺牲了速度,但是保证了数据安全。

来看一个不加锁的栗子:

import timedef sub():global numtemp = numtime.sleep(0.001)num = temp -1num = 100t_l = []
for i in range(100):t = threading.Thread(target=sub,args=())t.start()t_l.append(t)for t in t_l:t.join()print(num)

在上面这个程序中,我们开一百个线程,每个线程都对全局变量num实现-1的操作,如果顺利,最终num的值应该为0.
实际运行过程是这样的:
100个线程开始抢GIL,抢到的将被CPU执行:
step1: 执行global num
step2: temp = num 赋值操作
step3: 发生I/O阻塞,挂起,GIL释放 (下一步的num=temp-1 还未被执行,因此全局变量num的值仍然为100)
剩余的99个线程抢GIL锁,重复上面的步骤。
剩余的98个线程抢GIL锁,重复上面的步骤。
。。。
如果阻塞时间够长(比如大于0.1秒),在阻塞期间,100个线程都被被切换一遍的话,那么最终num的值是99;
如果阻塞时间短一点,在某个时刻,前面阻塞的线程恢复并抢到了GIL被CPU继续执行,那么执行num=temp-1赋值操作 ,全局变量num的值被改变,线程结束,下一个被执行的线程拿到的num值就是99……依次类推,最终num的值经过多次赋值操作后将变得不确定,这取决于有多多少线程从阻塞中恢复过来。
如果不阻塞的话,每个线程都会执行对num 赋值操作,下一个线程拿到的num就是上一个线程减一的结果,最终num的值归零。

下面我们进行加锁操作:
lock = threading.Lock() # 获取锁对象
lock.acquire() # 加锁
数据操作部分
lock.release() # 释放锁

import threading
import timedef sub(lock):global numlock.acquire()  #获得锁temp = numtime.sleep(0.01)num = temp -1lock.release()  # 执行完数据修改,释放锁num = 100lock = threading.Lock()
# 实例化一个用户锁/互斥锁,这个锁是全局变量,
# 每个线程获取到锁才能执行,执行完了释放,下一个线程才能获取锁
t_l = []
for i in range(100):t = threading.Thread(target=sub,args=(lock,))t.start()t_l.append(t)for t in t_l:t.join()print(num)

上面加锁和解锁的操作也可以通过上下文管理来实现:

with lock:数据修改部分

死锁和递归锁

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

import threading
import timeclass MyThread(threading.Thread):def run(self):self.foo()self.bar()def foo(self):LockA.acquire()print('%s got LockA '% self.name)LockB.acquire()print('%s got LockB'%self.name)LockB.release()LockA.release()# Thread-1在执行完foo函数后,释放锁。然后继续执行bar函数,重新获取锁。def bar(self):LockB.acquire()print('%s got LockB ' % self.name)time.sleep(1)   # 让Thread-1获得了LockB后阻塞,OS切换线程Thread-2,其先执行foo,获取到LockA,然后需要获取LockB,才能执行下去,才能释放锁。而LockB在Thread-1手中,Thread-1从阻塞中恢复,需要获得LockA才能继续执行下去,才能释放锁。于是两个线程互相等待,发生死锁。LockA.acquire() # 因为LockA已经被其它线程抢走了,所以这里卡死了。print('%s got LockA' % self.name)LockA.release()LockB.release()LockA = threading.Lock()
LockB = threading.Lock()for i in range(10):t = MyThread()t.start()'''死锁了
Thread-1 got LockA
Thread-1 got LockB
Thread-1 got LockB
Thread-2 got LockA
'''

解决方案就是使用递归锁:
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
rlock = threading.RLock() # 拿到一个可重入锁对象,将上面的所有锁都更换为rlock。

Semaphore信号量

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据。
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
锁信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念。
看一个栗子:

import threading
import multiprocessing
import time, os, randomdef go_wc(sem,name):with sem:print('员工%s 抢到一个茅坑,开始蹲...'% name)time.sleep(random.uniform(1,3))print('员工%s 完事了,感到身心愉悦...'% name)if __name__ == '__main__':  # 如果是进程的话,在windows系统下,进程必须写到if __name__ == '__main__':内,否则报错print('大家开始上厕所》》》')sem = threading.Semaphore(3)    # 设定最大为3# sem = multiprocessing.Semaphore(3)t_l = []for i in range(10):t = threading.Thread(target=go_wc, args=(sem,i))# t = multiprocessing.Process(target=go_wc,args=(sem,i))t.start()t_l.append(t)for t in t_l:t.join()print('大家都完事了《《《')'''
大家开始上厕所》》》
员工1 抢到一个茅坑,开始蹲...
员工0 抢到一个茅坑,开始蹲...
员工5 抢到一个茅坑,开始蹲...
员工0 完事了,感到身心愉悦...   同一时刻,只有3个坑,其中一个完事了,下一个才能开始
员工4 抢到一个茅坑,开始蹲...
'''

Event事件

在多线程环境中,每个线程的执行一般是独立的,如果一个线程的执行依赖于另一个线程的状态,那么就有必要引入某种标志位来进行判断,event就相当于一个全局的标志位。event常用于主线程控制其他线程的执行。
创建一个event对象:
event = threading.Event()
event对象的方法:
1. event.isSet() 或 event.is_set(), 返回event对象的bool值,event对象的初始bool值是False.
2. event.wait() 如果上面是True, 啥也不做,往下执行,如果上面是False, 则阻塞线程. wait(num)为超时设置,超过num秒,继续往下执行。
3. event.set() 设置event对象True
4. event.clear() 恢复为False
5. 图示:

上栗子:

import threading
import timedef request():print('waitting for server...')event.wait()  #阻塞,等待主线程开启服务器print('connecting to server....')if __name__ == '__main__':event = threading.Event()for i in range(5):t = threading.Thread(target=request)t.start()print('attemp to start server')time.sleep(3)event.set() # 开启服务器后,更改event状态

互斥锁、死锁、递归锁、信号量、Event相关推荐

  1. 036-2018-1028 线程 效率对比 数据共享 同步锁死锁递归锁 守护线程 信号量

    笔记 昨日内容回顾: 队列:Queue 管道 : Pipe ,recv消息的时候 OSError,EOFError,数据不安全的 Manager : 共享数据 , 数据不安全 , 加锁 进程池 : P ...

  2. 线程的创建 验证线程之间共享数据 守护线程 线程进程效率对比 锁 死锁 递归锁...

    线程(from threading import Thread):CPU调度的最小单位 线程的两种创建方式:方式一: 1 from threading import Thread 2 def f1(i ...

  3. java锁(公平锁和非公平锁、可重入锁(又名递归锁)、自旋锁、独占锁(写)/共享锁(读)/互斥锁、读写锁)

    前言 本文对Java的一些锁的概念和实现做个整理,涉及:公平锁和非公平锁.可重入锁(又名递归锁).自旋锁.独占锁(写)/共享锁(读)/互斥锁.读写锁 公平锁和非公平锁 概念 公平锁是指多个线程按照申请 ...

  4. Juc07_乐观锁和悲观锁、公平锁和非公平锁、递归锁(可重入锁)、死锁及排查、自旋锁

    文章目录 ①. 乐观锁和悲观锁 ②. 公平锁和非公平锁 ③. 可重入锁(又名递归锁) ④. 死锁及排查 ⑥. 自旋锁 ①. 乐观锁和悲观锁 ①. 悲观锁(synchronized关键字和Lock的实现 ...

  5. 你了解多线程自旋锁、互斥锁、递归锁等锁吗?

    首先看一下问题引出,先看一些经典的问题. 多线程的隐患 首先我们利用多线程的话肯定是好处多多,因为我们可以同时去做一些事情,大大的提高了效率.像我们下载视频的时候就可以同时下载多个视频,这样是节省了很 ...

  6. Java面试之锁-可重入锁和递归锁

    可重入锁和递归锁ReentrantLock 概念 可重入锁就是递归锁 指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取到该锁的代码. 在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取 ...

  7. java-15种锁之可重入锁(又名递归锁)

    1.什么是可重入锁(递归锁) 可重入锁(也叫递归锁):指的是同一线程外层函数获得锁之后,内层递归函数仍然可以获取该锁的代码,在同一线程在外层方法获取锁的时候+,在进入内层方法会自动获取锁. 也就是说, ...

  8. Java锁之可重入锁和递归锁

    Java锁之可重入锁和递归锁 目录 Java锁之可重入锁和递归锁基本概念 Java锁之可重入锁和递归锁代码验证 小结 理论,代码,小结,学习三板斧. 1. Java锁之可重入锁和递归锁基本概念 可重入 ...

  9. java -锁(公平、非公平锁、可重入锁【递归锁】、自旋锁)

    1.公平锁.非公平锁 2.可重入锁(递归锁) 3.自旋锁 AtomicReference atomicReference = new AtomicReference();//原子引用线程 下面代码5秒 ...

  10. python学习-----9.7-----GIL、死锁递归锁、信号量,event事件

    GIL介绍 GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全.每个进程内都会存在一把GIL,同 ...

最新文章

  1. 【最简洁】一句CSS3代码实现不规则自定义背景图拼接样式,多用于异形弹窗背景图
  2. 统计数据背后的指数分布模型
  3. Struts2 学习笔记 — 第一个struts2项目
  4. 对CSS了解-overflow:hidden
  5. GitLab - Ubuntu18搭建GitLab仓库服务器(转)
  6. spoj 2 Prime Generator
  7. Java基础---匿名对象的理解和使用
  8. Python中获取当前日期的格式
  9. 51单片机 外部时钟_基于51单片机的LCD12864显示模拟时钟
  10. mysql in操作_MySQL查询in操作排序
  11. window下hadoop、hbase的安装和eclipse开发环境配置
  12. 关于域名系统DNS解析IP地址的一些总结
  13. 计算机桌面维护面试题,100 | 运维常见面试题
  14. 【转】一文带你了解800万像素车载摄像头
  15. c语言表达式判断语法错误题,大连理工大学C语言模拟题机房题库单选、判断、填空(分章节_共十一章)...
  16. 几款比较有名的刷流量软件
  17. 无论多大年纪,请保留一份童真和幻想
  18. 无法访问此网站 localhost 拒绝了我们的连接请求。
  19. 给FLASH加链接的方法
  20. 章泽天加入微软实习 网友:提升程序员整体形象

热门文章

  1. (王道408考研数据结构)第八章排序-第四节2:快速排序
  2. extundelete应用实战
  3. UnhookWindowsHookEx
  4. USACO-Section1.3 Transformations (矩阵旋转匹配问题)
  5. 使用shell脚本或命令行添加、删除 crontab 定时任务
  6. Python三目运算符
  7. 使用redis数据库的目的?
  8. windows10双系统安装ubuntu18.04
  9. 机器学习的挑战:黑盒模型正面临这3个问题
  10. 什么是ETL?一文掌握ETL设计过程