二个需要注意的点:
1 线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock任然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来 2 join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

GIL  Lock

锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

保护不同的数据就应该加不同的锁。

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

过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限

  线程1抢到GIL锁,拿到执行权限,开始执行,然后加一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock....这就导致了串行运行的效果

  既然是串行,那我们执行

  t1.start()

  t1.join()

  t2.start()

  t2.join()

  这也是串行执行啊,为何要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。

from threading import Thread,Lock
import time
mutex=Lock() n=100 def task():
global n
# mutex.acquire()
tem=n
time.sleep(0.1)
n=tem-1
# mutex.release() l_list=[]
for i in range(100):
t=Thread(target=task)
t.start()
l_list.append(t) for t in l_list:
t.join() # print(n) #每结束一个线程都打印一次100个99
print(n) #打印的是结果 99
gil保证的是解释器代码的数据安全 让每个线程实现并发的效果

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其他线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,在调用release方法释放锁:

import threading

R=threading.Lock()

R.acquire()
'''
对公共数据的操作
'''
R.release()
from threading import Thread,Lock
import time
mutex=Lock() n=100 def task():
global n
mutex.acquire()
tem=n
time.sleep(0.1)
n=tem-1
mutex.release() l_list=[]
for i in range(100):
t=Thread(target=task)
t.start()
l_list.append(t) for t in l_list:
t.join() print(n) #每结束一个线程就打印一次99-0
print(n) #0最后结果 由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

GIL锁与互斥锁综合分析!!!

分析:

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

互斥锁与join的区别!!!

#不加锁:并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock
import os,time
def task():
global n
print('%s is running' %current_thread().getName())
temp=n
time.sleep(0.5)
n=temp-1 if __name__ == '__main__':
n=100
lock=Lock()
threads=[]
start_time=time.time()
for i in range(100):
t=Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join() stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n)) '''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
''' #不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import current_thread,Thread,Lock
import os,time
def task():
#未加锁的代码并发运行
time.sleep(3)
print('%s start to run' %current_thread().getName())
global n
#加锁的代码串行运行
lock.acquire()
temp=n
time.sleep(0.5)
n=temp-1
lock.release() if __name__ == '__main__':
n=100
lock=Lock()
threads=[]
start_time=time.time()
for i in range(100):
t=Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n)) '''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:53.294203758239746 n:0
''' #有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
#没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
#start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
#单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
from threading import current_thread,Thread,Lock
import os,time
def task():
time.sleep(3)
print('%s start to run' %current_thread().getName())
global n
temp=n
time.sleep(0.5)
n=temp-1 if __name__ == '__main__':
n=100
lock=Lock()
start_time=time.time()
for i in range(100):
t=Thread(target=task)
t.start()
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n)) '''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
''' 互斥锁与join的区别(重点!!!)

互斥锁与join的区别

死锁现象与递归锁

进程也有死锁与递归锁  

自定义锁一次acquire必须对应一次release,不能连续acquire

递归锁可连续的acquire,没acquire一次计数加一,针对的是第一个抢到我的人

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

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锁
然后就卡住,死锁了
'''

死锁

解决方法,递归锁,在python中为了支持在同一线程中多次请求统一资源,python童工了可重如锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了一个acquire的次数,从而使得资源可以被多次require(需求),直到一个线程中的所有acquire都被release,其他的线程才能获得资源。

递归锁

 

信号量Semaphore

同进程的一样

Semaphore管理一个内置的计数器

每当调用acquire()时内置计数器-1

没当release()时内置计时器+1

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

from threading import Thread,Semaphore
import time
import random
sm = Semaphore(5) # 五个厕所五把锁
# 跟你普通的互斥锁区别在于,普通的互斥锁是独立卫生间,所有人抢一把锁
# 信号量 公共卫生间 有多个坑,所有人抢多把锁 def task(name):
sm.acquire()
print('%s正在蹲坑'%name)
# 模拟蹲坑耗时
time.sleep(random.randint(1,5))
sm.release() if __name__ == '__main__':
for i in range(20):
t = Thread(target=task,args=('伞兵%s号'%i,))
t.start()

公共厕所

 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

from threading import Event,Thread
import time
import random event = Event() def light():
print('红灯亮着!')
time.sleep(3)
event.set() # 解除阻塞,给我的event发了一个信号
print('绿灯亮了!') def car(i):
print('%s 正在等红灯了'%i)
event.wait() # 阻塞
print('%s 加油门飙车了'%i) t1 = Thread(target=light)
t1.start() for i in range(5):
t = Thread(target=car,args=(i,))
t.start()

线程queue 

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

queue.Queue 先进先出

queue.LifoQueue 先进后出

queue.priorityQueue 设置优先级

import queue

q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get()) 1
2
3 import queue
q = queue.LifoQueue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print(q.get())
print(q.get())
print(q.get())
print(q.get()) 4
3
2
1 import queue
q = queue.PriorityQueue()
q.put((10,'a'))
q.put((-1,'b'))
q.put((100,'c'))
print(q.get())
print(q.get())
print(q.get()) (-1, 'b')
(10, 'a')
(100, 'c')

同步锁 死锁与递归锁 信号量 线程queue event事件的更多相关文章

  1. Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程

    Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...

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

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

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

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

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

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

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

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

  6. day 33 什么是线程? 两种创建方式. 守护线程. 锁. 死锁现象. 递归锁. GIL锁

    一.线程     1.进程:资源的分配单位    线程:cpu执行单位(实体) 2.线程的创建和销毁开销特别小 3.线程之间资源共享,共享的是同一个进程中的资源 4.线程之间不是隔离的 5.线程可不需 ...

  7. python并发编程之线程(创建线程,锁(死锁现象,递归锁),GIL锁)

    什么是线程 进程:资源分配单位 线程:cpu执行单位(实体),每一个py文件中就是一个进程,一个进程中至少有一个线程 线程的两种创建方式: 一 from threading import Thread ...

  8. Python之路(第四十四篇)线程同步锁、死锁、递归锁、信号量

    在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lock ...

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

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

随机推荐

  1. 秒懂数据类型的真谛—Python基础前传(4)

    一切编程语言都是人设计的,既然是人设计的,那么设计各种功能的时候就一定会有它的道理,那么设计数据类型的用意是什么呢? (一) 基本数据类型 基本数据类型: 数字 int 字符串 str 布尔值 boo ...

  2. Leetcode题目121.买卖股票的最佳时机(简单)

    题目描述: 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润. 注意你不能在买入股票前卖出 ...

  3. css实现元素在div底部显示

    #CSS .1 {position:relative;} .2 {;} #HTML <div class="1"> <div class="2" ...

  4. Python之Javascript

    1.JavaScript 被设计用来向 HTML 页面添加交互行为. 2.JavaScript 是一种脚本语言(脚本语言是一种轻量级的编程语言). 3.JavaScript 由数行可执行计算机代码组成 ...

  5. QT 多线程程序设计 -互斥

    QT通过三种形式提供了对线程的支持.它们分别是,一.平台无关的线程类,二.线程安全的事件投递,三.跨线程的信号-槽连接.这使得开发轻巧的多线程Qt程序更为容易,并能充分利用多处理器机器的优势.多线程编 ...

  6. 指定pip清华源

    临时指定: pip install cefpython3 -i https://pypi.tuna.tsinghua.edu.cn/simple 一直使用:pip的配置文件为%HOME%/pip/pi ...

  7. SR论文代码汇总

    1.SRCNN 页面  里面有论文,matlab和caffe代码. Tensorflow https://github.com/tegg89/SRCNN-Tensorflow 2.ESPCN 论文链接 ...

  8. shell 脚本基础与条件判断

    #!shell脚本格式决定专业性 #!/bin/bash #filename:脚本名 #author:作者 #date:时间 #脚本作用 脚本的执行方式  #脚本名为wk.sh 绝对路径 /root/ ...

  9. spark简单快速学习及打开UI界面---1

    1.远程集群测试 import org.apache.spark.{SparkContext, SparkConf} import scala.math.random /** * 利用spark进行圆 ...

  10. NLP基础

    1  自然语言处理三大特征抽取器(CNN/RNN/TF)比较 白衣骑士Transformer:盖世英雄站上舞台 华山论剑:三大特征抽取器比较 综合排名情况 以上介绍内容是从几个不同角度来对RNN/CN ...