Python (九) 協程以及數據庫操做

本節內容html

  1. Gevent協程
  2. Select\Poll\Epoll異步IO與事件驅動
  3. Python鏈接Mysql數據庫操作
  4. Paramiko SSH

 

協程

協程,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程python

協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。所以:程序員

協程能保留上一次調用時的狀態(即全部局部狀態的一個特定組合),每次過程重入時,就至關於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。sql

 

協程的好處:數據庫

  • 無需線程上下文切換的開銷
  • 無需原子操做鎖定及同步的開銷
  • 方便切換控制流,簡化編程模型
  • 高併發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。因此很適合用於高併發處理。

缺點:編程

  • 沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上.固然咱們平常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  • 進行阻塞(Blocking)操做(如IO時)會阻塞掉整個程序

使用yield實現協程操做例子    數組

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/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所有運行在主程序操做系統進程的內部,但它們被協做式地調度。安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 

同步與異步的性能區別 多線程

上面程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn。 初始化的greenlet列表存放在數組threads中,此數組被傳給gevent.joinall 函數,後者阻塞當前流程,並執行全部給定的greenlet。執行流程只會在 全部greenlet執行完後纔會繼續向下走。  

遇到IO阻塞時會自動切換任務

 

經過gevent實現單線程下的多socket併發

server side 

client side   

  

論事件驅動與異步IO

事件驅動編程是一種編程範式,這裏程序的執行流由外部事件來決定。它的特色是包含一個事件循環,當外部事件發生時使用回調機制來觸發相應的處理。另外兩種常見的編程範式是(單線程)同步以及多線程編程。

讓咱們用例子來比較和對比一下單線程、多線程以及事件驅動編程模型。下圖展現了隨着時間的推移,這三種模式下程序所作的工做。這個程序有3個任務須要完成,每一個任務都在等待I/O操做時阻塞自身。阻塞在I/O操做上所花費的時間已經用灰色框標示出來了。

 

在單線程同步模型中,任務按照順序執行。若是某個任務由於I/O而阻塞,其餘全部的任務都必須等待,直到它完成以後它們才能依次執行。這種明確的執 行順序和串行化處理的行爲是很容易推斷得出的。若是任務之間並無互相依賴的關係,但仍然須要互相等待的話這就使得程序沒必要要的下降了運行速度。

在多線程版本中,這3個任務分別在獨立的線程中執行。這些線程由操做系統來管理,在多處理器系統上能夠並行處理,或者在單處理器系統上交錯執行。這 使得當某個線程阻塞在某個資源的同時其餘線程得以繼續執行。與完成相似功能的同步程序相比,這種方式更有效率,但程序員必須寫代碼來保護共享資源,防止其 被多個線程同時訪問。多線程程序更加難以推斷,由於這類程序不得不經過線程同步機制如鎖、可重入函數、線程局部存儲或者其餘機制來處理線程安全問題,若是 實現不當就會致使出現微妙且使人痛不欲生的bug。

在事件驅動版本的程序中,3個任務交錯執行,但仍然在一個單獨的線程控制中。當處理I/O或者其餘昂貴的操做時,註冊一個回調到事件循環中,而後當 I/O操做完成時繼續執行。回調描述了該如何處理某個事件。事件循環輪詢全部的事件,當事件到來時將它們分配給等待處理事件的回調函數。這種方式讓程序盡 可能的得以執行而不須要用到額外的線程。事件驅動型程序比多線程程序更容易推斷出行爲,由於程序員不須要關心線程安全問題。

當咱們面對以下的環境時,事件驅動模型一般是一個好的選擇:

  1. 程序中有許多任務,並且…
  2. 任務之間高度獨立(所以它們不須要互相通訊,或者等待彼此)並且…
  3. 在等待事件到來時,某些任務會阻塞。

當應用程序須要在任務間共享可變的數據時,這也是一個不錯的選擇,由於這裏不須要採用同步處理。

網絡應用程序一般都有上述這些特色,這使得它們可以很好的契合事件驅動編程模型。

 

Select\Poll\Epoll異步IO 

 

 

selectors模塊

This module allows high-level and efficient I/O multiplexing, built upon the select module primitives. Users are encouraged to use this module instead, unless they want precise control over the OS-level primitives used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import selectors
import socket
 
sel = selectors.DefaultSelector()
 
def accept(sock, mask):
     conn, addr = sock.accept()  # Should be ready
     print ( 'accepted' , conn, 'from' , addr)
     conn.setblocking( False )
     sel.register(conn, selectors.EVENT_READ, read)
 
def read(conn, mask):
     data = conn.recv( 1000 # Should be ready
     if data:
         print ( 'echoing' , repr (data), 'to' , conn)
         conn.send(data)  # Hope it won't block
     else :
         print ( 'closing' , conn)
         sel.unregister(conn)
         conn.close()
 
sock = socket.socket()
sock.bind(( 'localhost' , 10000 ))
sock.listen( 100 )
sock.setblocking( False )
sel.register(sock, selectors.EVENT_READ, accept)
 
while True :
     events = sel.select()
     for key, mask in events:
         callback = key.data
         callback(key.fileobj, mask)
相關文章
相關標籤/搜索