進程、線程、協程

 先來欣賞一段有趣的漫畫對話哦html

 

     ————— 次日 —————python

 

 

 

 

 

 

 

 ——————————————————mysql

 

 

 

 

 


 

什麼事進程和線程

有必定基礎的小夥伴們確定都知道進程和線程。git

進程是什麼呢?github

直白地講,進程就是應用程序的啓動實例。好比咱們運行一個遊戲,打開一個軟件,就是開啓了一個進程。sql

進程擁有代碼和打開的文件資源、數據資源、獨立的內存空間。編程

線程又是什麼呢?安全

線程從屬於進程,是程序的實際執行者。一個進程至少包含一個主線程,也能夠有更多的子線程。併發

線程擁有本身的棧空間。框架

有了進程爲何還須要線程

由於進程不能同一時間只能作一個事情

什麼是線程

線程是操做系統調度的最小單位

線程是進程正真的執行者,是一些指令的集合(進程資源的擁有者)

同一個進程下的讀多個線程共享內存空間,數據直接訪問(數據共享)

爲了保證數據安全,必須使用線程鎖

GIL全局解釋器鎖

在python全局解釋器下,保證同一時間只有一個線程運行

防止多個線程都修改數據

線程鎖(互斥鎖)

GIL鎖只能保證同一時間只能有一個線程對某個資源操做,但當上一個線程還未執行完畢時可能就會釋放GIL,其餘線程就能夠操做了

線程鎖本質把線程中的數據加了一把互斥鎖

mysql中共享鎖 & 互斥鎖

mysql共享鎖:共享鎖,全部線程都能讀,而不能寫

mysql排它鎖:排它,任何線程讀取這個這個數據的權利都沒有

加上線程鎖以後全部其餘線程,讀都不能讀這個數據

有了GIL全局解釋器鎖爲何還須要線程鎖

由於cpu是分時使用的

死鎖定義

兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去

 進程和線程的痛點

線程之間是如何進行協做的呢?

最經典的例子就是生產者/消費者模式

若干個生產者線程向隊列中寫入數據,若干個消費者線程從隊列中消費數據。

應用程序像工廠,進程像車間,線程像工人

一個進程中的線程能夠在不一樣cpu上執行,一個線程不能同時在兩個cpu上執行

python中有一個全局解釋器鎖(GIL global interpreter lock),他就像一把鎖所在進程上,保證同一時刻,一個進程中不管有多少線程,只能保證有一個線程出來

隨着cpu核數的增長,python中能夠建立多個進程,一個進程中有一個全局解釋器,這樣一個cpu跑一個進程,一個進程同一時刻只能出一個線程,達到並行的目的

線程和進程的區別 :

線程是能夠共享內存的,而進程不能夠共享內存,一個進程就像一個應用程序,例如qq和淘寶,這屬於兩個進程,在QQ中發信息確定不會發到淘寶中,可是在qq中存在不少的線程,他們能夠共享內存,他們都屬於一個進程,

假如eclipse在運行時佔中400M的內存空間,那麼這個進程下的線程的共享空間爲這個主進程所佔用的內存空間,就是eclipse的線程的內存共享就爲400M

線程沒有辦法單獨執行,線程必須在進程中

多進程之間的通訊:

多進程之間沒法直接通訊,它必須藉助一個第三方的橋樑

1 隊列

from multiprocessing import Process, Queue 
def f(q,n): 
    q.put([ n, 'hello']) 
if __name__ == '__main__':
    q = Queue()

for i in range(5): 
        p = Process(target=f, args=(q,i)) 
        p.start() 
        print q.get() 

線程的安全問題,假如如今有十個線程,同時修改一個變量,讓這個變量+1,那麼會出現一個問題,假如A線程正在修改變量,如今變量爲m=2,A線程操做m,目的是讓A+1,然而A修改到一半的時候,線程B又進來了,線程B此時取的m的值也是2,A修改完了m=3,B修改完了m仍是等於3 ,很是不安全

解決辦法,就是加一把鎖,形象的例子:

假如如今有10我的要上廁所,鑰匙在我手裏,這個時候A進來,他把門關上了,別人就進不去,只有等A完過後其餘人才能進來,這個門就是控制線程的那把鎖

代碼實現就經過一個lock來實現,lock 只容許一個線程訪問

lock=threading.Lock()

lock.acquire()

lock.release()

代碼以下

val=0    #全局變量

def run(self,):
    #lock.acquire()
    time.sleep(1)
    global val 
    lock.acquire()
    val+=1
    print '%s' %val
    lock.release()        //這個地方執行完了必須relese,讓後面的線程繼續執行,不然後面沒法執行
lock=threading.Lock()
for i in range(100):
    t=threading.Thread(target=run,args=(i,))
    t.start()

假如一個廁所門裏有兩個坑,容許兩我的同時上廁所,即容許兩個線程同時修改變量,那麼用samp

samp = threading.BoundedSemaphore(3)

samp.acquire()

samp.release()

當參數爲1時,實際上就等於lock

val=0    #全局變量

def run(self,):
    #lock.acquire()
    time.sleep(1)
    global val 
    samp.acquire()
    val+=1
    print '%s' %val
    samp.release()
lock=threading.Lock()
samp = threading.BoundedSemaphore(3)
for i in range(100):
    t=threading.Thread(target=run,args=(i,))
    t.start()

全局變量和局部變量

全局變量是在方法以外定義的變量,方法內部不能修改全局變量

例如 val=0 #全局變量

def run(self,n)

val+=1

程序會報錯,方法內不能直接就該全局變量

在假如 val=0 #全局變量

def run(self,n)

val=1

print val

輸出結果會是1,這是爲何呢,應爲此時函數內部的val與全局變量val並非一個,函數內部的 val是局部變量,函數外部的val爲全局變量

假如 執行c=run(‘haha’)

print val

會輸出 0

1

假如要在函數內從新定義局部變量

必須在函數中從新聲明全局變量

val=0   #全局變量

    def run(self,n)

       global val

        val=1

       print val 

join() 執行到join時,例如執行到join(3)而後就在這個地方等待三秒,

from threading import Thread
import time
class MyThread(Thread):
    def run(self):
        time .sleep(5)
        print '我是線程'
def bar():
    print 'bar'
t1=MyThread(target=bar )
t1.start()
t1.join(3)
print ‘over'

採用面向類的生產者消費者模型

#_*_ coding:utf-8 _*_

from threading import Thread 
from Queue import Queue
import time
class Producer(Thread):
    def __init__(self,name,queue):
        self.__name=name
        self.__queue=queue
        super(Producer,self).__init__()                 #重寫了父類的方法
    def run(self):
        while True:
            if self.__queue.full():
                time.sleep(1)
            else :
                self.__queue.put('baozi')
                time.sleep(1)
                print '%s生產了一個包子'%(self.__name)
class consumer(Thread): def __init__(self,name,queue): self.__name=name self.__queue=queue super(consumer,self).__init__() def run(self): while True: if self.__queue.empty(): time.sleep(1) else : self.__queue.get() time.sleep(1) print '%s 消費一個包子'%(self.__name,) que =Queue(maxsize=100) baozi_make1=Producer('mahzongyi',que) baozi_make1.start() baozi_make2=Producer('mahzongyi1',que) baozi_make2.start() baozi_make3=Producer('mahzongyi2',que) baozi_make3.start() for item in range(20): name = 'lazup%d'%(item) temp=consumer(name,que) temp.start()

上面的代碼正確地實現了生產者/消費者模式,可是卻並非一個高性能的實現。爲何性能不高呢?緣由以下:

1.涉及到同步鎖。

2.涉及到線程阻塞狀態和可運行狀態之間的切換。

3.涉及到線程上下文的切換。

以上涉及到的任何一點,都是很是耗費性能的操做。

 

 

 


 

什麼事協程

協程,英文Coroutines,是一種比線程更加輕量級的存在。正如一個進程能夠擁有多個線程同樣,一個線程也能夠擁有多個協程。

協程微線程,纖程,本質是一個單線程

協程能在單線程處理高併發

線程遇到I/O操做會等待、阻塞,協程遇到I/O會自動切換(剩下的只有CPU操做)

線程的狀態保存在CPU的寄存器和棧裏而協程擁有本身的空間,因此無需上下文切換的開銷,因此快、

爲甚麼協程可以遇到I/O自動切換

協程有一個gevent模塊(封裝了greenlet模塊),遇到I/O自動切換

協程缺點

沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上

線程阻塞(Blocking)操做(如IO時)會阻塞掉整個程序

 

最重要的是,協程不是被操做系統內核所管理,而徹底是由程序所控制(也就是在用戶態執行)。

這樣帶來的好處就是性能獲得了很大的提高,不會像線程切換那樣消耗資源。

既然協程這麼好,它究竟是怎麼來使用的呢?

咱們來看一看python當中對協程的實現案例,一樣以生產者消費者模式爲例:

 

 這段代碼十分簡單,即便沒用過python的小夥伴應該也能基本看懂。

代碼中建立了一個叫作consumer的協程,而且在主線程中生產數據,協程中消費數據。

其中 yield 是python當中的語法。當協程執行到yield關鍵字時,會暫停在那一行,等到主線程調用send方法發送了數據,協程纔會接到數據繼續執行。

可是,yield讓協程暫停,和線程的阻塞是有本質區別的。協程的暫停徹底由程序控制,線程的阻塞狀態是由操做系統內核來進行切換。

所以,協程的開銷遠遠小於線程的開銷。

 

 

協程的應用

有哪些編程語言應用到了協程呢?咱們舉幾個栗子:

Lua語言

Lua從5.0版本開始使用協程,經過擴展庫coroutine來實現。

Python語言

正如剛纔所寫的代碼示例,python能夠經過 yield/send 的方式實現協程。在python 3.5之後,async/await 成爲了更好的替代方案。

Go語言

Go語言對協程的實現很是強大而簡潔,能夠輕鬆建立成百上千個協程併發執行。

Java語言

如上文所說,Java語言並無對協程的原生支持,可是某些開源框架模擬出了協程的功能,有興趣的小夥伴能夠看一看Kilim框架的源碼:

https://github.com/kilim/kilim

 

 

 

 

詳細介紹請閱讀個人分類Python基礎下的《Python三程三器的那些事》

相關文章
相關標籤/搜索