--- ---復元コンテンツ始まります
1.GILグローバルインタープリタロック
2.GIL通常のミューテックス
3.デッドロック
4.セマフォ
5.eventイベント
6.キュースレッドQ
高い同時TCPに基づく7.サプリメントは、スレッドを使用しています
.GILグローバルインタープリタロック
GILは何か:まずは、公式の説明を見てみましょう
CPythonとでは、このグローバルインタプリタロック、また、GILとして知られ、ミューテックス、
同時に、Pythonのバイトコードを実行するために複数のスレッドを防ぐため、これがロックしようとしている、と
非スレッドセーフなメモリ管理はCPythonので、および他の多くの特性はGILに依存し、
そこで彼は、プログラムの効率に影響を与える場合でも、直接削除することはできません
要約:CPythonのでは、並列スレッドがGILを低減効率が得られ、シリアルになるであろう
PS:知る必要は通訳がそうではCPython、そこPyPy、JPythonのとだけではないということです。
GILはまた、これは言語の問題ではなくCPythonのインタプリタのPythonの問題ではありません、だけではCPythonに存在します
2.GILの問題によって引き起こされます:
これは、最初の3つのステップに分けて明確なPYファイルを実行する必要があります
-
Pythonインタプリタがメモリにハードディスクからロードされます
-
ハードディスクからのPyファイルがメモリにロードされます
-
Pyのインタプリタは、CPUの実行にファイルの内容を解析し、
第二に、実装は、いつでもPYファイル、それはすぐにPythonインタプリタを起動することを明確にする必要があります
GILは、グローバルインタプリタロックと呼ばれるインタプリタに加え、mutexロック、ロックが最終的にアプリケーションへの影響はありですか?
これは、通訳の役割、および通訳やアプリケーションコードの間の関係を知る必要があります
コンテンツPYファイルの性質は、文字列であり、唯一のインタプリタを説明するとき、それはインタプリタは、命令実行システムをサポートするために、現在のシステムのコードPYを翻訳し、文法的な意味を持ちます。
子スレッド、実行すべきタスクを処理するためにターゲットサブスレッドを示すサブコードに割り当てられたスレッドを開いたとき。実行されるコードは、インタプリタに引き渡されなければならない、インタプリタは、競争によってもたらされるデータの共有を避けるために、ある複数のスレッド間で共有する必要があるので、通訳を与えるだろうミューテックスを追加しました!
ミューテックスの性質上、シリアルプログラムのため、データのセキュリティを確保し、効率を低下させ、GILは、プログラムの全体的な効率が低下することができます!
3.なぜ必要GILロック:
GILとGC 在使用Python中进行编程时,程序员无需参与内存的管理工作,这是因为Python有自带的内存管理机制,简称GC。那么GC与GIL有什么关联? 要搞清楚这个问题,需先了解GC的工作原理,Python中内存管理使用的是引用计数,每个数会被加上一个整型的计数器,表示这个数据被引用的次数,当这个整数变为0时则表示该数据已经没有人使用,成了垃圾数据。 当内存占用达到某个阈值时,GC会将其他线程挂起,然后执行垃圾清理操作,垃圾清理也是一串代码,也就需要一条线程来执行。 示例代码:
from threading import Thread
def task():
a = 10
print(a)
# 开启三个子线程执行task函数
Thread(target=task).start()
Thread(target=task).start()
Thread(target=task).start()
上述代码的内存结构如下:
通过上图可以看出,GC与其他线程都在竞争解释器的执行权,而CPU何时切换,以及切换到哪个线程都是无法预支的,这样一来就造成了竞争问题,假设线程1正在定义变量a=10,而定义变量第一步会先到到内存中申请空间把10存进去,第二步将10的内存地址与变量名a进行绑定,如果在执行完第一步后,CPU切换到了GC线程,GC线程发现10的地址引用计数为0则将其当成垃圾进行了清理,等CPU再次切换到线程1时,刚刚保存的数据10已经被清理掉了,导致无法正常定义变量。
有了GIL后,多个线程不可能在同一时间使用解释器,从而保证了解释器的数据安全
GIL的加锁时机:在调用解释器时立即加锁
解锁时机:
当前线程遇到了IO时释放
当前线程执行时间超过设定值时释放
GIL锁有优点也有缺点:
优点:
保证了数据的安全
缺点:
互斥锁的特性使得多线程无法并行
研究python的多线程是否有用需要分情况讨论
四个任务 计算密集型的 10s
单核情况下
开线程更省资源
多核情况下
开进程 10s
开线程 40s
四个任务 IO密集型的
单核情况下
开线程更节省资源
多核情况下
开线程更节省资源
案例:计算密集型
1 from multiprocessing import Process 2 from threading import Thread 3 import os,time 4 def work(): 5 res=0 6 for i in range(100000000): 7 res*=i 8 9 10 if __name__ == '__main__': 11 l=[] 12 print(os.cpu_count()) # 本机为6核 13 start=time.time() 14 for i in range(6): 15 # p=Process(target=work) #耗时 4.732933044433594 16 p=Thread(target=work) #耗时 22.83087730407715 17 l.append(p) 18 p.start() 19 for p in l: 20 p.join() 21 stop=time.time() 22 print('run time is %s' %(stop-start))
案例:IO密集型
1 from multiprocessing import Process 2 3 import os,time 4 def work(): 5 time.sleep(2) 6 7 if __name__ == '__main__': 8 l=[] 9 print(os.cpu_count()) #本机为6核 10 start=time.time() 11 for i in range(4000): 12 p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上 13 # p=Thread(target=work) #耗时2.051966667175293s多 14 l.append(p) 15 p.start() 16 for p in l: 17 p.join() 18 stop=time.time() 19 print('run time is %s' %(stop-start))
总结:
python的多线程到底有没有用
需要看情况而定 并且肯定是有用的
多进程+多线程配合使用
二.GIL与普通的互斥锁区别
GIL保护的是解释器级别的数据安全,比如对象的引用计数,垃圾分代数据等等,具体参考垃圾回收机制详解。
对于程序中自己定义的数据则没有任何的保护效果,这一点在没有介绍GIL前我们就已经知道了,所以当程序中出现了共享自定义的数据时就要自己加锁,如下例:
1 from threading import Thread 2 import time 3 4 n = 100 5 6 7 def task(): 8 global n 9 tmp = n 10 time.sleep(1) 11 n = tmp -1 12 13 t_list = [] 14 for i in range(100): 15 t = Thread(target=task) 16 t.start() 17 t_list.append(t) 18 19 for t in t_list: 20 t.join() 21 22 print(n)
三.死锁
死锁问题
当程序出现了不止一把锁,分别被不同的线程持有, 有一个资源 要想使用必须同时具备两把锁
这时候程序就会进程无限卡死状态 ,这就称之为死锁
案例:
案例:
from threading import Thread RLock import time mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() print('%s抢到了A锁' % self.name) mutexA.release() print('%s释放了A锁' % self.name) mutexB.release() print('%s释放了B锁' % self.name) for i in range(10): t = MyThread() t.start()
这样就会产生死锁现象:解释
首先执行的是func1然后线程1抢到A锁,其他九个线程需要的需要等待A锁被释放,
线程1不会释放A锁,紧接着就会抢B锁,B锁是没有人抢的,所以直接就可以拿到B锁,
然后释放B锁,紧接着释放A锁,那么其他九个在等待的线程看到A锁被线程1释放了,
他们就会立马区抢,那么线程1已经区执行func2了,因为其他九个线程都在func1哪里抢,
线程1直接就可以抢到B锁,然后线程1会进入阻塞状态1秒,但是线程1还持有者B锁,
func1里面的线程也开始抢B锁了,1秒阻塞状态过去,线程1也需要抢A锁了,
但是A锁被func1里面的线程持有,这样就会产生我要你的A锁,你要我的B锁,
但是都给不了,就会卡住
补充知识点:
Rlock 称之为递归锁或者可重入锁
与Lock唯一的区别: Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次 如果一个线程已经执行过acquire 其他线程将无法执行acquire
案例:
1 from threading import Thread,Lock,current_thread,RLock 2 import time 3 """ 4 Rlock可以被第一个抢到锁的人连续的acquire和release 5 每acquire一次锁身上的计数加1 6 每release一次锁身上的计数减1 7 只要锁的计数不为0 其他人都不能抢 8 9 """ 10 # mutexA = Lock() 11 # mutexB = Lock() 12 mutexA = mutexB = RLock() # A B现在是同一把锁 13 14 15 class MyThread(Thread): 16 def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 17 self.func1() 18 self.func2() 19 20 def func1(self): 21 mutexA.acquire() 22 print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name 23 mutexB.acquire() 24 print('%s抢到了B锁'%self.name) 25 mutexB.release() 26 print('%s释放了B锁'%self.name) 27 mutexA.release() 28 print('%s释放了A锁'%self.name) 29 30 def func2(self): 31 mutexB.acquire() 32 print('%s抢到了B锁'%self.name) 33 time.sleep(1) 34 mutexA.acquire() 35 print('%s抢到了A锁' % self.name) 36 mutexA.release() 37 print('%s释放了A锁' % self.name) 38 mutexB.release() 39 print('%s释放了B锁' % self.name) 40 41 for i in range(10): 42 t = MyThread() 43 t.start()
四.信号量
可以现在被锁定的代码 同时可以被多少线程并发访问
互斥锁: 锁住一个马桶 同时只能有一个
信号量: 锁住一个公共厕所 同时可以来一堆人
用途: 仅用于控制并发访问 并不能防止并发修改造成的问题
案例:
1 from threading import Semaphore,Thread 2 import time 3 import random 4 5 sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 6 7 def task(name): 8 sm.acquire() 9 print('%s占了一个坑位'%name) 10 time.sleep(random.randint(1,3)) 11 sm.release() 12 13 for i in range(40): 14 t = Thread(target=task,args=(i,)) 15 t.start()
五.event事件
什么是事件
因为不同线程之间是独立运行的状态不可预测,所以一个线程与另一个线程间的数据是不同步的,当一个线程需要利用另一个线程的状态来确定自己的下一步操作时,就必须保持线程间数据的同步,Event就可以实现线程间同步
可用的一些方法:
event.isSet():返回event的状态值;
event.wait():将阻塞线程;知道event的状态为True
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False
案例:
1 from threading import Event,Thread 2 import time 3 4 # 先生成一个event对象 5 e = Event() 6 7 8 def light(): 9 print('红灯正亮着') 10 time.sleep(3) 11 e.set() # 发信号 12 print('绿灯亮了') 13 14 def car(name): 15 print('%s正在等红灯'%name) 16 e.wait() # 等待信号 17 print('%s加油门飙车了'%name) 18 19 t = Thread(target=light) 20 t.start() 21 22 for i in range(10): 23 t = Thread(target=car,args=('伞兵%s'%i,)) 24 t.start()
六.线程q对列
同一个进程下的多个线程本来就是数据共享 为什么还要用队列 因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题 因为锁操作的不好极容易产生死锁现象
1.Queue 先进先出队列
案例:
q = Queue(3) q.put(1) q.put(2) q.put(3) print(q.get(timeout=1)) print(q.get(timeout=1)) print(q.get(timeout=1))
2.LifoQueue 后进先出队列
案例:
lq = LifoQueue() lq.put(1) lq.put(2) lq.put(3) print(lq.get()) print(lq.get()) print(lq.get())
3.PriorityQueue 优先级队列
案例:
pq = PriorityQueue() # 数字优先级 pq.put((10,"a")) pq.put((11,"a")) pq.put((-11111,"a")) print(pq.get()) print(pq.get()) print(pq.get()) # 字符串优先级 pq.put(("b","a")) pq.put(("c","a")) pq.put(("a","a")) print(pq.get()) print(pq.get()) print(pq.get())
补充:如何解决基于TCP的高并发
server:
1 import socket 2 from threading import Thread 3 4 """ 5 服务端 6 1.要有固定的IP和PORT 7 2.24小时不间断提供服务 8 3.能够支持并发 9 """ 10 11 server = socket.socket() 12 server.bind(('127.0.0.1',8080)) 13 server.listen(5) 14 15 16 def talk(conn): 17 while True: 18 try: 19 data = conn.recv(1024) 20 if len(data) == 0:break 21 print(data.decode('utf-8')) 22 conn.send(data.upper()) 23 except ConnectionResetError as e: 24 print(e) 25 break 26 conn.close() 27 28 while True: 29 conn, addr = server.accept() # 监听 等待客户端的连接 阻塞态 30 print(addr) 31 t = Thread(target=talk,args=(conn,)) 32 t.start()
client:
1 import socket 2 3 4 client = socket.socket() 5 client.connect(('127.0.0.1',8080)) 6 7 while True: 8 client.send(b'hello') 9 data = client.recv(1024) 10 print(data.decode('utf-8'))