1.什麼是線程
線程是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務
一個線程是一個執行上下文,這是一個CPU的全部信息須要執行一系列的指令。
假設你正在讀一本書,你如今想休息一下,可是你但願可以回來,恢復從你中止的位置。實現這一點的方法之一是經過草草記下頁碼、行號和數量。因此你讀一本書的執行上下文是這三個數字。
若是你有一個室友,她使用相同的技術,她能夠把書當你不使用它,並繼續閱讀,她停了下來。而後你就能夠把它拿回來,恢復你在哪裏。
線程在相同的方式工做。CPU是給你的錯覺同時作多個計算。它經過花一點時間在每一個計算。它能夠這樣作,由於它有一個爲每一個計算執行上下文。就像你能夠與你的朋友分享一本書,不少任務能夠共享CPU。
更多的技術水平,一個執行上下文(所以一個線程)由CPU的寄存器的值。
最後:線程不一樣於流程。執行線程的上下文,而進程是一羣資源與計算有關。一個過程能夠有一個或多個線程。
澄清:與流程相關的資源包括內存頁(一個進程中的全部線程都有相同的視圖的內存),文件描述符(如。、打開的套接字)和安全憑據(如。,用戶的ID開始這個過程)。python
2.什麼是進程
一個執行程序被稱爲過程的實例。
每一個進程提供了所需的資源來執行一個程序。進程的虛擬地址空間,可執行代碼,打開系統處理對象,一個安全上下文,一個獨特的進程標識符,環境變量,優先類,最小和最大工做集大小和至少一個線程的執行。每一個流程開始一個線程,一般被稱爲主要的線程,但從它的任何線程能夠建立額外的線程。算法
3.進程與線程的區別數據庫
4.Python GIL(Global Interpreter Lock)
全局解釋器鎖在CPython的,或GIL,是一個互斥鎖,防止多個本地線程執行Python字節碼。這把鎖是必要的,主要是由於CPython的內存管理不是線程安全的。(然而,因爲GIL存在,其餘功能已經習慣於依賴保證執行)。
首先須要明確的一點是GIL並非Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就比如C++是一套語言(語法)標準,可是能夠用不一樣的編譯器來編譯成可執行代碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也同樣,一樣一段代碼能夠經過CPython,PyPy,Psyco等不一樣的Python執行環境來執行。像其中的JPython就沒有GIL。然而由於CPython是大部分環境下默認的Python執行環境。因此在不少人的概念裏CPython就是Python,也就想固然的把GIL歸結爲Python語言的缺陷。因此這裏要先明確一點:GIL並非Python的特性,Python徹底能夠不依賴於GIL
參考文檔:http://www.dabeaz.com/python/UnderstandingGIL.pdf編程
多線程相似於同時執行多個不一樣程序,多線程運行有以下優勢:安全
使用線程能夠把佔據長時間的程序中的任務放到後臺去處理。服務器
在其餘線程正在運行時,線程能夠暫時擱置(也稱爲睡眠) -- 這就是線程的退讓。網絡
1.threading模塊多線程
直接調用:
import threading
import time併發
def code(num): #定義每一個線程要運行的函數 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=code,args=(1,)) #生成一個線程實例 t2 = threading.Thread(target=code,args=(2,)) #生成另外一個線程實例 t1.start() #啓動線程 t2.start() #啓動另外一個線程 print(t1.getName()) #獲取線程名 print(t2.getName()) 或者: #!/usr/bin/env python #coding:utf-8 import threading import time class A(object):#定義每一個線程要運行的函數 def __init__(self,num): self.num = num self.run() def run(self): print('線程',self.num) time.sleep(1) for i in range(10): t = threading.Thread(target=A,args=(i,))#生成一個線程實例 target對應你要執行的函數名 t.start()#啓動線程
繼承類調用:app
import threading import time class MyThread(threading.Thread):#繼承threading.Thread def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定義每一個線程要運行的函數 print("我是第%s個程序" %self.num) time.sleep(3)#執行結束後等待三秒 if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start() 或者: import threading import time class MyThread(threading.Thread):#繼承threading.Thread def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定義每一個線程要運行的函數 print("我是第%s個程序" %self.num) time.sleep(3)#執行結束後等待三秒 if __name__ == '__main__': for i in range(10): t = MyThread(i) t.start()
上述代碼建立了10個「前臺」線程,而後控制器就交給了CPU,CPU根據指定算法進行調度,分片執行指令
import threading
首先導入threading 模塊,這是使用多線程的前提。
2.Join & Daemon
1).join方法的做用是阻塞主進程(擋住,沒法執行join之後的語句),專一執行多線程。
2).多線程多join的狀況下,依次執行各線程的join方法,前頭一個結束了才能執行後面一個。
3).無參數,則等待到該線程結束,纔開始執行下一個線程的join。
4.設置參數後,則等待該線程這麼長時間就無論它了(而該線程並無結束)。
無論的意思就是能夠執行後面的主進程了。
例如:
若是不使用join
import time import threading def run(n): print('正在運行[%s]\n' % n) time.sleep(2) print('運行結束--') def main(): for i in range(5): t = threading.Thread(target=run,args=[i,]) #time.sleep(1) t.start() t.join(1) print('進行中的線程名', t.getName()) #第一個執行的 m = threading.Thread(target=main,args=[]) m.start() print("---main thread done----") print('繼續往下執行')
結果以下:
---main thread done---- #線程還沒結束就執行 正在運行[0] 繼續往下執行 #線程還沒結束就執行 進行中的線程名 Thread-2 正在運行[1] 運行結束-- 進行中的線程名 Thread-3 正在運行[2] 運行結束-- 進行中的線程名 Thread-4 正在運行[3] 運行結束-- 進行中的線程名 Thread-5 正在運行[4] 運行結束-- 進行中的線程名 Thread-6 運行結束--
若是使用join:
import time import threading def run(n): print('正在運行[%s]\n' % n) time.sleep(1) print('運行結束--') def main(): for i in range(5): t = threading.Thread(target=run,args=[i,]) t.start() t.join(1) print('進行中的線程名', t.getName()) #第一個執行的 m = threading.Thread(target=main,args=[]) m.start() m.join()#開啓join print("---main thread done----") #結果是線程執行完畢以後 才執行 print('繼續往下執行') #結果是線程執行完畢以後 才執行
注:join(time)等time秒,若是time內未執行完就不等了,繼續往下執行
以下:
import time import threading def run(n): print('正在運行[%s]\n' % n) time.sleep(1) print('運行結束--') def main(): for i in range(5): t = threading.Thread(target=run,args=[i,]) #time.sleep(1) t.start() t.join(1) print('進行中的線程名', t.getName()) #第一個執行的 m = threading.Thread(target=main,args=[]) m.start() m.join(timeout=2) #設置時間 print("---main thread done----") print('繼續往下執行')
結果:
正在運行[0] 進行中的線程名 Thread-2 運行結束-- 正在運行[1] 運行結束-- 進行中的線程名 Thread-3 ---main thread done---- #執行了 繼續往下執行 #執行了 正在運行[2] 運行結束-- 進行中的線程名 Thread-4 正在運行[3] 運行結束-- 進行中的線程名 Thread-5 正在運行[4] 運行結束-- 進行中的線程名 Thread-6
一些線程作後臺任務,好比發送keepalive包,或執行垃圾收集週期,等等。這些只是有用的主程序運行時,它能夠殺死他們一旦其餘,非守護線程退出。
沒有守護程序線程,你要跟蹤他們,和告訴他們退出,您的程序能夠徹底退出。經過設置它們做爲守護進程線程,你可讓他們運行和忘記他們,當程序退出時,任何守護程序線程自動被殺。
import time import threading def run(n): print('正在運行[%s]\n' % n) time.sleep(1) print('運行結束--') def main(): for i in range(5): t = threading.Thread(target=run,args=[i,]) time.sleep(1) t.start() t.join(1) print('進行中的線程名', t.getName()) #第一個執行的 m = threading.Thread(target=main,args=[]) m.setDaemon(True)#將主線程設置爲Daemon線程,它退出時,其它子線程會同時退出,無論是否執行完任務 m.start() print("---main thread done----") print('繼續往下執行')
注意:守護程序線程忽然停在關閉。他們的資源(如打開的文件、數據庫事務,等等)可能不會正常發佈。若是你想讓你的線程中止優雅,讓他們non-daemonic和使用合適的信號機制等
一個進程下能夠啓動多個線程,多個線程共享父進程的內存空間,也就意味着每一個線程能夠訪問同一份數據,此時,若是2個線程同時要修改同一份數據那就會出現數據修改會被不是一個進程修改
因爲線程之間是進行隨機調度,而且每一個線程可能只執行n條執行以後,CPU接着執行其餘線程。因此,可能出現以下問題:
import time import threading def addNum(ip): global num #在每一個線程中都獲取這個全局變量 print('--get num:',num,'線程數',ip ) time.sleep(1) num +=1 #對此公共變量進行-1操做 num_list.append(num) num = 0 #設定一個共享變量 thread_list = [] num_list =[] for i in range(10): t = threading.Thread(target=addNum,args=(i,)) t.start() thread_list.append(t) for t in thread_list: #等待全部線程執行完畢 t.join() print('final num:', num ) print(num_list)
結果:
--get num: 0 線程數 0 --get num: 0 線程數 1 --get num: 0 線程數 2 --get num: 0 線程數 3 --get num: 0 線程數 4 --get num: 0 線程數 5 --get num: 0 線程數 6 --get num: 0 線程數 7 --get num: 0 線程數 8 --get num: 0 線程數 9 final num: 10 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
正常來說,這個num結果應該是0, 但在python 2.7上多運行幾回,會發現,最後打印出來的num結果不老是0,爲何每次運行的結果不同呢? 哈,很簡單,假設你有A,B兩個線程,此時都 要對num 進行減1操做, 因爲2個線程是併發同時運行的,因此2個線程頗有可能同時拿走了num=100這個初始變量交給cpu去運算,當A線程去處完的結果是99,但此時B線程運算完的結果也是99,兩個線程同時CPU運算的結果再賦值給num變量後,結果就都是99。那怎麼辦呢? 很簡單,每一個線程在要修改公共數據時,爲了不本身在還沒改完的時候別人也來修改此數據,能夠給這個數據加一把鎖, 這樣其它線程想修改此數據時就必須等待你修改完畢並把鎖釋放掉後才能再訪問此數據。
*注:不要在3.x上運行,不知爲何,3.x上的結果老是正確的,多是自動加了鎖
加上鎖以後
import time import threading def addNum(): global num #在每一個線程中都獲取這個全局變量 print('--get num:',num ) time.sleep(1) lock.acquire() #修改數據前加鎖 num -=1 #對此公共變量進行-1操做 lock.release() #修改後釋放 num = 100 #設定一個共享變量 thread_list = [] lock = threading.Lock() #生成全局鎖 for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待全部線程執行完畢 t.join() print('final num:', num )
RLock(遞歸鎖)
說白了就是在一個大鎖中還要再包含子鎖
Semaphore(信號量)
互斥鎖 同時只容許一個線程更改數據,而Semaphore是同時容許必定數量的線程更改數據 ,好比廁全部3個坑,那最多隻容許3我的上廁所,後面的人只能等裏面有人出來了才能再進去。
event
一個事件是一個簡單的同步對象;
事件表明一個內部國旗,和線程
能夠等待標誌被設置,或者設置或清除標誌自己。
事件= threading.Event()
、#客戶端線程等待國旗能夠設置
event.wait()#服務器線程能夠設置或重置它
event.set()
event.clear()
若是設置了國旗,等方法不作任何事。
若是標誌被清除,等待會阻塞,直到它再次成爲集。
任意數量的線程可能等待相同的事件。
Python提供了Event對象用於線程間通訊,它是由線程設置的信號標誌,若是信號標誌位真,則其餘線程等待直到信號接觸。
Event對象實現了簡單的線程通訊機制,它提供了設置信號,清楚信號,等待等用於實現線程間的通訊。
1 設置信號
使用Event的set()方法能夠設置Event對象內部的信號標誌爲真。Event對象提供了isSet()方法來判斷其內部信號標誌的狀態。當使用event對象的set()方法後,isSet()方法返回真
2 清除信號
使用Event對象的clear()方法能夠清除Event對象內部的信號標誌,即將其設爲假,當使用Event的clear方法後,isSet()方法返回假
3 等待
Event對象wait的方法只有在內部信號爲真的時候纔會很快的執行並完成返回。當Event對象的內部信號標誌位假時,則wait方法一直等待到其爲真時才返回。
事件處理的機制:全局定義了一個「Flag」,若是「Flag」值爲 False,那麼當程序執行 event.wait 方法時就會阻塞,若是「Flag」值爲True,那麼event.wait 方法時便再也不阻塞。
clear:將「Flag」設置爲False
set:將「Flag」設置爲True
案例:
#!/usr/bin/env python #codfing:utf-8 #__author__ = 'yaoyao' import threading def do(event): print ('最早執行') event.wait() print ('最後執行') event_obj = threading.Event() for i in range(10): t = threading.Thread(target=do, args=(event_obj,)) t.start() print ('開始等待') event_obj.clear() inp = input('輸入true:') if inp == 'true': event_obj.set()
queque隊列:
隊列是特別有用在線程編程時必須在多個線程之間交換安全信息。
class queue.Queue(maxsize=0) #先入先出
class queue.LifoQueue(maxsize=0) #last in fisrt out
class queue.PriorityQueue(maxsize=0) #存儲數據時可設置優先級的隊列
構造函數爲一個優先隊列。最大尺寸是整數集upperbound限制數量的物品能夠放在隊列中。插入塊一旦達到這個尺寸,直到隊列項。若是最大尺寸小於或等於零,隊列大小是無限的。
生產者消費模型
案例:
#!/usr/bin/env python #codfing:utf-8 from multiprocessing import Process import threading import time def foo(i): print ('開始',i) if __name__ == "__main__": for i in range(10): p = Process(target=foo,args=(i,)) p.start() print('我是華麗的分隔符')
進程各自持有一份數據,默認沒法共享數據
好比:
#!/usr/bin/env python #codfing:utf-8 #__author__ = 'yaoyao' from multiprocessing import Process li = [] def foo(i): li.append(i) print ('進程裏的列表是',li) if __name__ == '__main__': for i in range(10): p = Process(target=foo,args=(i,)) p.start() print ('打開列表 是空的',li)
顯示以下:
打開列表 是空的 [] 進程裏的列表是 [0] 打開列表 是空的 [] 進程裏的列表是 [2] 打開列表 是空的 [] 進程裏的列表是 [3] 打開列表 是空的 [] 進程裏的列表是 [1] 打開列表 是空的 [] 進程裏的列表是 [5] 打開列表 是空的 [] 進程裏的列表是 [4] 打開列表 是空的 [] 打開列表 是空的 [] 進程裏的列表是 [6] 打開列表 是空的 [] 進程裏的列表是 [7] 打開列表 是空的 [] 進程裏的列表是 [8] 打開列表 是空的 [] 進程裏的列表是 [9]
共享數據兩種方法:
Array
#!/usr/bin/env python
#codfing:utf-8
#author = 'yaoyao'
from multiprocessing import Process,Array
temp = Array('i', [11,22,33,44])
def Foo(i):
temp[i] = 100+i
for item in temp:
print (i,'----->',item)
if name == "main":
for i in range(1):
p = Process(target=Foo,args=(i,))
p.start()
2.manage.dict()
協程
協程,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程。
協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。所以:
協程能保留上一次調用時的狀態(即全部局部狀態的一個特定組合),每次過程重入時,就至關於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。
協程的好處:
無需線程上下文切換的開銷 無需原子操做鎖定及同步的開銷 方便切換控制流,簡化編程模型 高併發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。因此很適合用於高併發處理。
缺點:
沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上.固然咱們平常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。 進行阻塞(Blocking)操做(如IO時)會阻塞掉整個程序
使用yield實現協程操做例子
import time
import queue
def consumer(name):
print("--->starting eating baozi...")
while True:
new_baozi = yield
print("[%s] is eating baozi %s" % (name,new_baozi))
#time.sleep(1)
def producer():
r = con.__next__() r = con2.__next__() n = 0 while n < 5: n +=1 con.send(n) con2.send(n) print("\033[32;1m[producer]\033[0m is making baozi %s" %n )
if name == 'main':
con = consumer("c1")
con2 = consumer("c2")
p = producer()
Greenlet
from greenlet import greenlet
def test1():
print 12
gr2.switch()
print 34
gr2.switch()
def test2():
print 56
gr1.switch()
print 78
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
Gevent
Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。
import gevent
def foo():
print('Running in foo')
gevent.sleep(0)
print('Explicit context switch to foo again')
def bar():
print('Explicit context to bar')
gevent.sleep(0)
print('Implicit context switch back to bar')
gevent.joinall([
gevent.spawn(foo),
gevent.spawn(bar),])
輸出:
Running in foo Explicit context to bar Explicit context switch to foo again Implicit context switch back to bar