11.python之線程,協程,進程,

一,進程與線程

1.什麼是線程
線程是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務
一個線程是一個執行上下文,這是一個CPU的全部信息須要執行一系列的指令。
假設你正在讀一本書,你如今想休息一下,可是你但願可以回來,恢復從你中止的位置。實現這一點的方法之一是經過草草記下頁碼、行號和數量。因此你讀一本書的執行上下文是這三個數字。
若是你有一個室友,她使用相同的技術,她能夠把書當你不使用它,並繼續閱讀,她停了下來。而後你就能夠把它拿回來,恢復你在哪裏。
線程在相同的方式工做。CPU是給你的錯覺同時作多個計算。它經過花一點時間在每一個計算。它能夠這樣作,由於它有一個爲每一個計算執行上下文。就像你能夠與你的朋友分享一本書,不少任務能夠共享CPU。
更多的技術水平,一個執行上下文(所以一個線程)由CPU的寄存器的值。
最後:線程不一樣於流程。執行線程的上下文,而進程是一羣資源與計算有關。一個過程能夠有一個或多個線程。
澄清:與流程相關的資源包括內存頁(一個進程中的全部線程都有相同的視圖的內存),文件描述符(如。、打開的套接字)和安全憑據(如。,用戶的ID開始這個過程)。python

2.什麼是進程
一個執行程序被稱爲過程的實例。
每一個進程提供了所需的資源來執行一個程序。進程的虛擬地址空間,可執行代碼,打開系統處理對象,一個安全上下文,一個獨特的進程標識符,環境變量,優先類,最小和最大工做集大小和至少一個線程的執行。每一個流程開始一個線程,一般被稱爲主要的線程,但從它的任何線程能夠建立額外的線程。算法

3.進程與線程的區別數據庫

  1. 線程共享建立它的進程的地址空間,進程有本身的地址空間。
  2. 線程直接訪問的數據段過程;過程有本身的複製父進程的數據段。
  3. 線程能夠直接與其餘線程的通訊過程,過程必須使用進程間通訊和同胞交流過程。
  4. 新建立的線程很容易;新工藝須要複製父進程。
  5. 線程能夠鍛鍊至關大的控制線程相同的過程;流程只能控制子進程。
  6. 主線程變動(取消、優先級變化等)可能會影響進程的其餘線程的行爲;父進程的變化不會影響子進

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編程

2、多線程

多線程相似於同時執行多個不一樣程序,多線程運行有以下優勢:安全

  1. 使用線程能夠把佔據長時間的程序中的任務放到後臺去處理。服務器

  2. 用戶界面能夠更加吸引人,這樣好比用戶點擊了一個按鈕去觸發某些事件的處理,能夠彈出一個進度條來顯示處理的進度
  3. 程序的運行速度可能加快
  4. 在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。在這種狀況下咱們能夠釋放一些珍貴的資源如內存佔用等等。
  5. 線程在執行過程當中與進程仍是有區別的。每一個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。可是線程不可以獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
  6. 每一個線程都有他本身的一組CPU寄存器,稱爲線程的上下文,該上下文反映了線程上次運行該線程的CPU寄存器的狀態。
  7. 指令指針和堆棧指針寄存器是線程上下文中兩個最重要的寄存器,線程老是在進程獲得上下文中運行的,這些地址都用於標誌擁有線程的進程地址空間中的內存。
  8. 線程能夠被搶佔(中斷)。
  9. 在其餘線程正在運行時,線程能夠暫時擱置(也稱爲睡眠) -- 這就是線程的退讓。網絡

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 模塊,這是使用多線程的前提。

  • start 線程準備就緒,等待CPU調度
  • setName 爲線程設置名稱
  • getName 獲取線程名稱
  • setDaemon 設置爲後臺線程或前臺線程(默認)
    若是是後臺線程,主線程執行過程當中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,均中止
    若是是前臺線程,主線程執行過程當中,前臺線程也在進行,主線程執行完畢後,等待前臺線程也執行完成後,程序中止
  • join 逐個執行每一個線程,執行完畢後繼續往下執行,該方法使得多線程變得無心義
  • run 線程被cpu調度後執行Thread類對象的run方法

2.Join & Daemon

join

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

daemon

一些線程作後臺任務,好比發送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限制數量的物品能夠放在隊列中。插入塊一旦達到這個尺寸,直到隊列項。若是最大尺寸小於或等於零,隊列大小是無限的。

生產者消費模型

2、多進程

案例:

#!/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]

共享數據兩種方法:

  1. 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

!/usr/bin/env python

-- coding:utf-8 --

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

相關文章
相關標籤/搜索