一、互斥锁 

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

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

模拟抢票实例:

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. React-native Android环境搭建

    基础安装 安装Homebrew Homebrew是Mac OSX的包管理器,我们需要通过Homebrew安装开发React Native的相关软件包. 如果不知道怎样安装Homebrew可以点这里:官 ...

  2. 【Java Web】新手教程(转)

    转自:http://www.journaldev.com/1854/java-web-application-tutorial-for-beginners#web-server-client Web ...

  3. Windows 修改个性化时间显示

    A goal is a dream with a deadline. Much effort, much prosperity. 我感觉我的时间显示不够人性化.不够个性化 修改注册表 我的系统为Win ...

  4. NOIP 合唱队形

    描述 N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形. 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…, ...

  5. Linux系统运行级别配置

    Linux的运行级别 Linux的运行级别有七种,可以通过查看/etc/inittab文件进行了解: Level0:系统停机状态,默认系统运行级别不能设置为0,否则系统不能正常启动: Level1:单 ...

  6. EF Code-First 学习之旅 多对多的关系

    public class Student { public Student() { this.Courses = new HashSet<Course>(); } public int S ...

  7. HttpClient发送Json数据到指定接口

    项目中遇到将Json数据发送到指定接口,于是结合网上利用HttpClient进行发送. /** * post发送json数据 * @param url * @param param * @return ...

  8. javascript异步编程的几种方法

    目前工作中用的比较多的异步模式编程有如下几种方法 一 回调函数 这是异步编程最基本的方法,假设有两个函数f1和f2,后者等待前者的执行结果 f1(); f2(); 如果f1是一个很耗时的任务,可以考虑 ...

  9. 浅谈 session 会话的原理

    先谈 cookie 网络传输基于的Http协议,是无状态的协议,即每次连接断开后再去连接,服务器是无法判断此次连接的客户端是谁. 如果每次数据传输都需要进行连接和断开,那造成的开销是很巨大的. 为了解 ...

  10. spring mvc : 中文传值(post/get)中文乱码

    请将以下代码放入 web.xml文件中,注意存放顺序,否则会报错.filter应该放在context-param后面: <!-- 字符过滤器 --> <filter> < ...