一、GIL全局解释锁

在Cpython解释器才有GIL的概念,不是python的特点

在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。

1.GIL介绍

GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都是一样,都是将并发运行变成串行,以此来保证数据的安全性。用来阻止同一个进程下的多个线程的同时执行。保护不同的数据的安全,就应该加不同的锁。

每次执行python程序,都会产生一个独立的进程,每个py文件都会产生独立的python进程。在一个python进程中,不仅有这个文件的主线程和开启的其他线程,还有解释器开启的垃圾回收机制等解释器级别的线程。

GIL存在是因为Cpython解释器的内存管理不是线程安全的:(这个内存管理指的是垃圾回收机制)

#1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(运行的py文件代码以及Cpython解释器的所有代码)
例如:test.py定义一个函数work,在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。 #2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

综上所述:在同一进程中多个线程在执行代码的时候,多个线程先访问到解释器的代码,拿到执行权限,然后将代码交给解释器去执行。解释器的代码是所有线程共享的,所以垃圾回收线程也是可以可能访问到解释器的代码去执行,这就导致了一个问题,当线程刚刚创建了一个数字100,还没来得及和一个变量名绑定关系,这个时候如果垃圾回收机制来检测到了,会直接把没有绑定关系的数字100删除,考虑到这种问题,所以出现GIL全局解释锁,保证python解释器同一时间只能执行一个任务的代码。

2.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的过程

GIL锁与互斥锁综合分析(重点!!!)

#互斥锁与join的区别

join是等待某个子线程或者子线程所有代码执行完毕,相当于锁住了整个代码
Lock只是锁住一部分操作数据的代码 两者之间是有很大差距的

3.GIL和多线程

有了GIL的存在,在同一时刻同一进程里面只有一个线程被执行。

那python的多线程没法利用多核的优势,是不是就没用了?

研究python的多线程是否有用需要分情况讨论:

假如有四个任务 计算密集型的情况下

单核情况下:开多个进程只会增加内存的开销,多线程开销更少,线程更节省资源

多核情况下:多核意味着并行计算,多进程可以把任务分散给每一个CPU,但是线程在一个进程中同一时刻只有一个线程执行用不上多核。进程更节省资源

假如有四个任务 I/O密集型的情况下

单核情况下:多进程增加资源消耗,而且进程的切换速度远不如线程,线程更节省资源

多核情况下:进程再多的核也解决不了I/O问题,都会进入阻塞态,而且还消耗资源,线程更节省资源

应用:多线程用于IO密集型:如socket,爬虫,web    多进程用于计算密集型:如金融分析

二、死锁现象和递归锁

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

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

解决方法使用递归锁:RLock。RLock可以被第一个抢到锁的人连续的acquire和release,每acquire一次,锁计数加1,每release一次,锁计数减1,只要锁的

计数不为0,其他线程都只能等待,等待该线程释放完所有锁。

三、信号量Semaphore

Semaphore 管理一个内置的计数器,每当调用acquire()是内置计数-1,调用release()是内置计数器+1,计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()

"""
互斥锁:一个厕所(一个坑位)
信号量:公共厕所(多个坑位)
"""
from threading import Semaphore,Thread
import time
import random sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 def task(name):
sm.acquire()
print('%s占了一个坑位'%name)
time.sleep(random.randint(1,3))
sm.release() for i in range(40):
t = Thread(target=task,args=(i,))
t.start() #同时创建了40个线程,5个坑位的公共厕所,开始有五个线程去占,睡不同时间之后,释放锁,同时其他线程拿到锁,最多只能有五个线程拿到锁。

信号量例子

四、event事件    让线程等待线程

from threading import Event,Thread
import time # 先生成一个event对象
e = Event() def light():
print('红灯正亮着')
time.sleep(3)
e.set() # 发信号
print('绿灯亮了') def car(name):
print('%s正在等红灯'%name)
e.wait() # 等待信号
print('%s加油门飙车了'%name) t = Thread(target=light) #线程运行light函数
t.start() for i in range(10):
t = Thread(target=car,args=('伞兵%s'%i,))
t.start() #线程先执行light函数,然后运行car函数,睡3秒,运行e.set()及下面的代码,同时,e.wait在等待信号,接收到信号之后也执行下面的代码。

五、线程queue

同一个进程下的多个线程本来就是数据共享 为什么还要用队列,因为队列是管道+锁,使用队列你就不需要自己手动操作锁的问题,因为锁操作不好极容易产生死锁现象。

import queue

q = queue.Queue()   #先进先出
q.put('hahha')
print(q.get()) #haha q = queue.LifoQueue() #last in first out 后进先出 和堆栈先进后出一样
q.put(1)
q.put(2)
q.put(3)
print(q.get()) #3 q = queue.PriorityQueue()
# 数字越小 优先级越高
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get()) #(-10, 'yyyy')

六、TCP服务端实现并发

将不同的功能尽量拆分成不同的函数拆分出来的功能可以被多个地方使用

1.将连接循环和通信循环拆分成不同的函数
2.将服务端通信循环做成多线程

服务端

import socket
from threading import Thread server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5) def talk(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0:break
print(data.decode('utf-8'))
conn.send(data.upper())
except ConnectionResetError as e:
print(e)
break
conn.close() while True:
conn, addr = server.accept() # 监听 等待客户端的连接 阻塞态
t = Thread(target=talk,args=(conn,))
t.start()

客户端

import socket

client = socket.socket()
client.connect(('127.0.0.1',8080)) while True:
client.send(b'hello')
data = client.recv(1024)
print(data.decode('utf-8'))

GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发的更多相关文章

  1. GIL 信号量 event事件 线程queue

    GIL全局解释器锁 官方解释: In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple n ...

  2. 8.14 day32 TCP服务端并发 GIL解释器锁 python多线程是否有用 死锁与递归锁 信号量event事件线程q

    TCP服务端支持并发 解决方式:开多线程 服务端 基础版 import socket """ 服务端 1.要有固定的IP和PORT 2.24小时不间断提供服务 3.能够支 ...

  3. 20191031:GIL全局解释锁

    20191031:GIL全局解释锁 总结关于GIL全局解释锁的个人理解 GIl全局解释锁,本身不是Python语言的特性,而是Python语言底层的c Python解释器的一个特性.在其他解释器中是没 ...

  4. ~~并发编程(十一):GIL全局解释锁~~

    进击のpython ***** 并发编程--GIL全局解释锁 这小节就是有些"大神"批判python语言不完美之处的开始 这一节我们要了解一下Cpython的GIL解释器锁的工作机 ...

  5. 子进程回收资源两种方式,僵尸进程与孤儿进程,守护进程,进程间数据隔离,进程互斥锁,队列,IPC机制,线程,守护线程,线程池,回调函数add_done_callback,TCP服务端实现并发

    子进程回收资源两种方式 - 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源. - 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源. from multipr ...

  6. python并发编程-多线程实现服务端并发-GIL全局解释器锁-验证python多线程是否有用-死锁-递归锁-信号量-Event事件-线程结合队列-03

    目录 结合多线程实现服务端并发(不用socketserver模块) 服务端代码 客户端代码 CIL全局解释器锁****** 可能被问到的两个判断 与普通互斥锁的区别 验证python的多线程是否有用需 ...

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

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

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

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

  9. python中的GIL(全局解释锁)多线程能够提升效率

    预启动的时候,应用程序仍然会调用 OnLaunched 方法的,在 OnLaunched 方法调用之后,会马上发生 Suspending 事件,随后应用就会暂停. 我先基于develop主分支拉出一个 ...

随机推荐

  1. call JSON.parse JSON.stringify typeof 的使用及严格模式this的使用

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  2. cogs 998. [東方S2] 帕秋莉·诺蕾姬

    二次联通门 : cogs 998. [東方S2] 帕秋莉·诺蕾姬 交上去后发现自己没上榜 就想着加点黑科技 把循环展开一下 结果WA了.. 万恶的姆Q /* cogs 998. [東方S2] 帕秋莉· ...

  3. 【luoguP1858】多人背包

    链接 对于每个状态\(f[j]\)多记录一个维度,转移的时候利用类似于归并排序的方法合并,以保证时间复杂度可以承受 注意事项:前\(K\)大可以有重复的价值 #include<iostream& ...

  4. Vue绑定事件,双向数据绑定,只是循环没那么简单

    v-on对象处理 <p @mouseover = "doTish" @mouseout = "doThat"> 对象形式 </p> &l ...

  5. Pytorch中ndarray tensor list互转

    1.ndarray->tensor : b=torch.from_numpy(a) 2.tensor->ndarray: b=a.numpy() ''' 但这么写会报错-- Runtime ...

  6. Debian 环境安装新版 nginx

    在 Debian 系统中,我们可以通过 apt-get 安装系统自带的 nginx,这样安装的 nginx 版本略旧.Nginx 官网提供了一些编辑绎好的 deb 安装包,我们只需更新安装源,就可以通 ...

  7. How to receive a million packets per second

    Last week during a casual conversation I overheard a colleague saying: "The Linux network stack ...

  8. 【MySQL】Mysql模糊查询like提速优化

    一般情况下like模糊查询的写法为(field已建立索引): SELECT `column` FROM `table` WHERE `field` like '%keyword%'; 上面的语句用ex ...

  9. 重装系统之前需要做的checklist

    1. 各浏览器 ---- 导出收藏夹 2. 备份桌面 3. 查用工具截图保存.保存使用了哪些工具 4.查看C盘有没有放置其他资料,需要备份的

  10. 增量ETL (长周期指标) 优化方案

    在日常数据处理过程中避免不了要计算跨长周期数据指标统计需求,类似于如下: 1.  统计每个城市(过去30天)用户浏览次数: 统计每个城市(本年)用户浏览次数: 统计每个城市(历史至今)用户浏览次数: ...