一、互斥锁 

用互斥锁,目的:局部串行(保护自己的数据

  进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理(即局部实行串行)。

模拟抢票实例:

from multiprocessing import Process,Lock
import json,os,time,random def search():
with open('db.txt',encoding='utf-8')as f:
dict = json.load(f)
print('%s 剩余票数 %s'%(os.getpid(),dict['count'])) def get():
with open('db.txt',encoding='utf-8') as reaf_f:
dic = json.load(reaf_f) if dic['count']>0:
dic['count'] -= 1
time.sleep(random.randint(1,3)) # 模拟手速,网速
with open('db.txt','w',encoding='utf-8') as write_f:
json.dump(dic,write_f)
print('%s 抢票成功' %os.getpid())
else:
print('剩余票数为%s,购票失败'%dic['count']) def task(mutex):
search() # 20个人都可以并发的查询票数
mutex.acquire() # 加锁
get() #通过锁,查询到结果的20人通过竞争逐一买票。前一个释放锁后后一个才可以进入,即串行
mutex.release() # 解锁 if __name__ == '__main__':
mutex = Lock()
for i in range(20): # 20个人抢票
p = Process(target=task,args=(mutex,))
p.start()

二、GIL互斥锁(保护解释器级别)
 
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。为什么会这样呢?

1、python程序执行顺序

  如执行test.py程序,都会开启python一个进程,代码中包含多个线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内,毫无疑问。

  执行流程:多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行,解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,而这把锁就是GIL,保证python解释器同一时间只能执行一个任务的代码。

2、GIL锁介绍

  GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理,如下图主进程中开启了两个线程,由于线程1先拿到解释器的GIL锁,故向cpu传送指令执行此线程,若此线程在执行过程遇到IO阻塞,被解释器强行要求释放GIL锁,线程2进入解释器执行相应代码,等线程1再次拿到解释器的权限时,继续执行其剩余程序。

3.多线程与GIL

  有了GIL存在,同一时刻同一进程中只有一个线程被执行。但是多线程仍然存在它的意义,解释如下:

  案例:我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:

方案一:开启四个进程,方案二:一个进程下,开启四个线程。

#单核情况下,分析结果:
  如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
  如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜

#多核情况下,分析结果:
  如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
  如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

  结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

应用(总结):
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析
1. 每个cpython进程内都有一个GIL
2. GIL导致同一进程内多个进程同一时间只能有一个运行
3. 之所以有GIL,是因为Cpython的内存管理不是线程安全的
4. 对于计算密集型用多进程,多IO密集型用多线程

(1)计算密集型实例

  多线程:

from threading import Thread
import time
def work():
res=0
for i in range(100000000):
res*=i
if __name__ == '__main__':
start=time.time()
result=[]
for i in range(4):#因为4核,所以开启4个进程
p=Thread(target=work)
result.append(p)
p.start()
for p in result:
p.join()
end=time.time()
print(end-start) #24.468851566314697

  多进程:

from multiprocessing import Process
import time
def work():
res=0
for i in range(100000000):
res*=i
if __name__ == '__main__':
start=time.time()
result=[]
for i in range(4):#因为4核,所以开启4个进程
p=Process(target=work)
result.append(p)
p.start()
for p in result:
p.join()
end=time.time()
print(end-start) #15.74721097946167

  分析:对于多进程情况,所开4个进程分别在4个cpu上同时进行执行,所花时间为开启进程时间和单个进程运行时间的总和,对于多线程情况,其都在竞争解释器权限,排队进行执行代码,所化时间为四个线程的总和。多线程花费时间未与多进程花费时间呈4倍关系是因为开启进程开销比开启线程开销大很多。

(2)IO密集型实例

  多线程:

from threading import Thread
import time
def work():
time.sleep(2)
if __name__ == '__main__':
start=time.time()
result=[]
for i in range(400):
p=Thread(target=work)
result.append(p)
p.start()
for p in result:
p.join()
end=time.time()
print(end-start) #2.049804925918579

  多进程

from multiprocessing import Process
import time
def work():
time.sleep(2)
if __name__ == '__main__':
start = time.time()
result = []
for i in range(400):
p = Process(target=work)
result.append(p)
p.start()
for p in result:
p.join()
end = time.time()
print(end - start) # 27.082503080368042

分析:对于多线程情况,线程遇到阻塞,便会切换到另个线程,所化总时间就为花时间最长的单个线程的时间,对于多进程情况,同时开启这么多进程会花费很多时间,其次单个进程执行遇到阻塞时,若没有其他任务也会继续等待阻塞结束。

三、死锁与递归锁

  对于互斥锁,只能acquire一次锁

1、死锁现象

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

from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock() class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('\033[41m%s 拿到A锁\033[0m' %self.name) mutexB.acquire()
print('\033[42m%s 拿到B锁\033[0m' %self.name)
mutexB.release() mutexA.release() def func2(self):
mutexB.acquire()
print('\033[43m%s 拿到B锁\033[0m' %self.name)
time.sleep(2) mutexA.acquire()
print('\033[44m%s 拿到A锁\033[0m' %self.name)
mutexA.release() mutexB.release() if __name__ == '__main__':
for i in range(10):
t=MyThread()
t.start() '''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
''

2、递归锁 (可以acquire多次)

  为解决上述死锁现象,出现了递归锁的概念:在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

from threading import Thread,RLock
import time
mutexA=mutexB=RLock() class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s 拿到A锁' %self.name) mutexB.acquire()
print('%s 拿到B锁' %self.name)
mutexB.release() mutexA.release() def func2(self):
mutexB.acquire()
print('%s 拿到B锁' %self.name)
time.sleep(2) mutexA.acquire()
print('%s 拿到A锁' %self.name)
mutexA.release() mutexB.release() if __name__ == '__main__':
for i in range(10):
t=MyThread()
t.start()

分析:一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止。

GIL、死锁与递归锁的更多相关文章

  1. 10 并发编程-(线程)-GIL全局解释器锁&死锁与递归锁

    一.GIL全局解释器锁 1.引子 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势 首先需要明确的一点是GIL并不是Python的特性,它是在实现Pyt ...

  2. GIL锁、死锁、递归锁、定时器

    GIL (Global Interpreter Lock) 锁 '''定义:In CPython, the global interpreter lock, or GIL, is a mutex th ...

  3. GIL全局解释器锁、死锁、递归锁、线程队列

    目录 GIL全局解释锁 多线程的作用 测试计算密集型 IO密集型 死锁现象 递归锁 信号量(了解) 线程队列 GIL全局解释锁 GIL本质上是一个互斥锁. GIL是为了阻止同一个进程内多个进程同时执行 ...

  4. GIL全局解释器锁-死锁与递归锁-信号量-event事件

    一.全局解释器锁GIL: 官方的解释:掌握概念为主 """ In CPython, the global interpreter lock, or GIL, is a m ...

  5. [并发编程 - 多线程:信号量、死锁与递归锁、时间Event、定时器Timer、线程队列、GIL锁]

    [并发编程 - 多线程:信号量.死锁与递归锁.时间Event.定时器Timer.线程队列.GIL锁] 信号量 信号量Semaphore:管理一个内置的计数器 每当调用acquire()时内置计数器-1 ...

  6. 26 python 初学(线程、同步锁、死锁和递归锁)

    参考博客: www.cnblogs.com/yuanchenqi/articles/5733873.html 并发:一段时间内做一些事情 并行:同时做多件事情 线程是操作系统能够进行运算调度的基本单位 ...

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

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

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

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

  9. Python并发编程-进程 线程 同步锁 线程死锁和递归锁

    进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...

随机推荐

  1. CSS 边距和填充

    margin and padding are the two most commonly used properties for spacing-out elements. A margin is t ...

  2. 20145222黄亚奇《网络对抗》MSF基础应用

    实践目标 掌握metasploit的基本应用方式. 具体需要完成(1)ms08_067;(2)ms11_050:(3)Adobe(4)成功应用任何一个辅助模块. 实验内容 掌握metasploit的基 ...

  3. shell-最近7天目录

    #采用将最近7天的日期放入到数组中,遍历整个目录,将这7天的目录连接成一个字符串paths. #注意: .日期目录里面的文件只是做了简单的以part开头的匹配. # .path路径是日期的上一层,以/ ...

  4. Android 在 SElinux下 如何获得对一个内核节点的访问权限【转】

    本文转载自:https://blog.csdn.net/wh_19910525/article/details/45170755 Android 5.0下,因为采取了SEAndroid/SElinux ...

  5. CODE FESTIVAL 2015 決勝(部分)

    CODE FESTIVAL 2015 決勝(部分) B - ダイスゲーム 题意: 给\(N\)个\(6\)个面骰子,然后问掷到概率最大的点数是多少. 分析: 随便打表随便发现是\(\huge\left ...

  6. 企业微信小程序--从零开始(带你见证从头开始的企业小程序之开发运营)

    1.注册微信小程序账户(自己摸索吧很简单的) 2.微信小程序认证 3.遇到的问题 1)

  7. vs2013 浏览器 browserlink 不停访问

  8. ixgbe RSS原理分析

    这个月,一直在搞ixgbe RSS,希望能使得收包均衡,结果没成功,但是对网卡的收包原理理解得更深入些. 1.网卡硬件通过网线或者光纤收包. 2.网卡的RSS功能根据网络五元组计算得到32bit的ha ...

  9. js里面如何才能让成员方法去调用类中其他成员

    function fun(){ var _this = this; //如果函数是用var定义的私有函数,如下 var func1 = function(){ } //那么类中其他函数都可以直接通过f ...

  10. Spring -- spring结合aop 进行 tx&aspectj事务管理配置方法

    1. tx 配置方法, 代码示例 javabean及其映射文件省略,和上篇的一样 CustomerDao.java, dao层接口 public interface CustomerDao { pub ...