I.はじめに
「'」 に定義されています。 ではCPythonと、グローバルインタプリタロック、またはGILを、複数防ぎミューテックスです 。このロックは、主に必要な、一度バイトコードはPythonの実行からネイティブスレッドを CPythonののAPOSメモリ管理はスレッドセーフにしない(ただし、以来、動作しているので。でGIL 。EXISTS、他の機能は)強制ことを保証でITに依存するように成長してきている 「」 ' マルチコアを利用することができない、実行のCPythonのインタプリタ、同じプロセスの下でオープンマルチスレッド、同時に一つだけのスレッド:結論優位
PS:pythonの通訳、最も一般的なの多くがあるCPythonのインタプリタがされる
と同時に、データのセキュリティを確保するために、シリアル犠牲効率になります:GILも、本質的にmutexが
同時に同じプロセスで複数のスレッドを防ぐために実行します(複数の同じプロセス内のスレッドが、同時達成するために、並行して達成することができない)は
、メモリ管理CPythonのインタプリタはスレッドセーフではないためGILが存在します
二.GILはじめに
GILは、すべてのミューテックスの本質であるので、本質的に、ミューテックス、ミューテックスで同一であり、全てなるシリアルが同時に実行され、同じ時間内でのデータの共有を制御するためにのみタスクによって修飾することができます、したがって、データのセキュリティを確保します。
異なるデータの安全性を守るために、あなたは別のロックを追加する必要がありますのを確認するためには、このです。
最初に一つのことを決定、GILを理解するには、次の各時間のpythonプログラムを、それが別のプロセスを持つことになります。例えばのpython test.py、Pythonのaaa.py、Pythonのbbb.pyは、3つの異なるプロセスパイソンが生成されます
'' ' #Pythonのtest.py検証プロセスのみが生成されます #のtest.pyコンテンツ のインポートOSを、時間 を印刷(os.getpid()) time.sleep(1000) ' '' test.pyのpython3 #を窓の下の タスクリスト| Pythonのはfindstr #Linuxでの PS AUX | グレップのPythonの プロセスのみが生成されますのpython test.pyを確認
だけでなく、インタプリタレベルtest.pyはメインスレッド又は他のスレッドChengkaiチー本線から、ならびにインタプリタスレッド短い、すべてのスレッドでは、ガベージコレクション、等オンプロセスでPythonは、このプロセスで実行されています内部に、は間違いありません
#1のすべてのデータは、共有され、データコードは、すべてのスレッド(すべてのコードとCPythonのインタプリタtest.pyすべてのコード)によって共有されているように 、例えば:(コード内容機能test.py作業を定義)以下に示すように、プロセス内のすべてのスレッドが動作するようにコードにアクセスすることができますので、我々は3つのスレッドを開き、手段にアクセスするには、ターゲット・コードを指し示すことができ、あなたが行うことができるということです。 #2タスクのすべてのスレッドは、タスクがすべてのスレッド、自分のタスクを実行するためには、あなたが最初のインタプリタコードにアクセスする能力があるアドレスに必要なコードを実行するインタプリタにパラメータとしてのコードが必要になります。
要約すると:
ターゲット=仕事の複数のスレッドは、プロセスが実行される場合
複数のスレッドが最初のコードを実行するターゲット・コード・インタプリタに、その後の許可を取得するために実行されるインタプリタコードを訪問し、
インタプリタコードは、すべてのスレッドで共有されているので、ガベージコレクタスレッドはまた、質問につながる、離れインタプリタコードの実行へのアクセスを有することができる、同じデータ100を同時にスレッド1のx = 100を実行すること、及びGILに示すように、ガベージコレクションを回復動作100に巧妙な方法によって行われ、この問題は解決されていない、つまり、ロック処理は、同時にコードのPythonインタプリタが1つのタスクのみを実行することができることを確認します
III。有用パイソンのマルチスレッドの研究
10S計算集約型のタスク4
シングルコアの場合
、オープンスレッドより省資源
マルチコア・ケース
オープンプロセス10sを
開いスレッド40代
の4 IO集約型のタスク
のシングルコアケース
オープンスレッドがより多くのリソースを節約する
マルチコア・ケースを
開いたスレッドより多くのリソースを節約
#计算密集型 from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) # 本机为8核 start=time.time() for i in range(6): p=Process(target=work) #耗时 7.551327705383301 # p=Thread(target=work) #耗时 27.445183515548706 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
from multiprocessing import Process from threading import Thread import threading import os,time def work(): time.sleep(2) if __name__ == '__main__': l=[] print(os.cpu_count()) #本机为8核 start=time.time() for i in range(4000): p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上 # p=Thread(target=work) #耗时2.051966667175293s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
四.GIL和普通互斥锁
from threading import Thread import time n = 100 def task(): global n tmp = n # time.sleep(1) n = tmp -1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n)
1.当程序中的time.sleep(1)开启后,代码执行到这的时候会进入阻塞态,这时候GIL锁会被释放,别的线程可以进行抢锁,在睡的这1秒的时间中,开启的100个线程都可以轮一遍,都可以抢到锁,所以每个线程中拿到的tmp都为100,在执行玩睡眠时间后执行后面的代码,所以结果为99
2.当未开启time.sleep(1)时,开启100个线程,每次只有一个线程可以拿到GIL锁,拿到的线程可以对数据进行操作,别的进程只能等到其释放锁后才能进行抢锁,之后再去执行代码,所以结果为0
五.死锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
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()
六.递归锁
死锁的解决方法就是递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
from threading import Thread,Lock,RLock import time mutexA=mutexB=RLock() 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()
七.信号量(Semaphore)
互斥锁:相当于一个厕所,只有一个坑位
信号量:相当于一个公共厕所,其中有多个坑位
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(10): t = Thread(target=task,args=(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 # 先生成一个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) t.start() for i in range(10): t = Thread(target=car,args=('伞兵%s'%i,)) t.start()
九.线程Queue
queue队列 :使用import queue,用法与进程Queue一样
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
1.Queue(先进先出)
import queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(先进先出): first second third '''
2.LifoQueue(先进后出,相当于堆栈)
import queue q=queue.LifoQueue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(后进先出): third second first '''
3.PriorityQueue(设置优先级,数字越小级别越高,级别可为负数)
import queue q=queue.PriorityQueue() #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q.put((20,'a')) q.put((-10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) ''' 结果(数字越小优先级越高,优先级高的优先出队): (-10, 'b') (20, 'a') (30, 'c') '''