python quaternion,g的python實現_Python Gevent

 2023-11-19 阅读 34 评论 0

摘要:參考資料 Python腳本的執行效率一直來說并不是很高,特別是Python下的多線程機制,長久以來一直被人們詬病。很多人都在思考如何讓Python執行的更快一些,其中典型的方式包括: 將復雜的代碼轉由C語言完成 多進程并發執行 多線程完成IO操作 python qu

參考資料

Python腳本的執行效率一直來說并不是很高,特別是Python下的多線程機制,長久以來一直被人們詬病。很多人都在思考如何讓Python執行的更快一些,其中典型的方式包括:

將復雜的代碼轉由C語言完成

多進程并發執行

多線程完成IO操作

python quaternion。然后,人們討論的更多的則是Gevent的協程機制。在理解Gevent之前,我們需要弄明白幾個基本的概念:進程(Process)、線程(Thread)、協程(Coroutine)、計算機IO方式等。

協程

進程和線程都是操作系統中的模型,操作系統通過進程和線程這兩種模型來執行程序。進程是操作系統分配資源(如CPU、內存等)和調度的基本單位,可以將其看作是包含系統分配的資源和執行流兩部分,通過進程模型,操作系統可以靈活地管理程序的執行。線程是執行流,一般而言一個進程只包含一個執行流,也就是說一個進程只包含一個線程,通過線程模型,一個進程可以擁有多個執行流,進而提供程序的處理能力。

協程coroutine其實是corporate routine的縮寫,直譯為協同的例程,簡稱為協程。在Linux中線程是輕量級的進程,因此也將協程coroutine稱為輕量級的線程,又稱為微線程。協程簡單的理解就是程序的執行流,在暫停和再次執行的過程中可以記錄當前的狀態,在暫停后需要再次執行時可以從暫停前的狀態繼續執行。協程暫停執行時,可以調度到另一個協程執行,這兩個協程之間的關系是對等的。

協程和生成器generator的概念很像,生成器也可以保存當前執行狀態并再次運行時恢復之前的狀態,不過區別在于協程暫停時可以調度到另一個協程執行,而生成器暫停時會由它的調用者繼續執行。

協程的調度由使用者所決定,而不像進程和線程那樣由操作系統進行調度,Gevent中的協程在暫停時會執行一個稱為Hub的協程,由Hub選擇處于等待執行狀態的協程繼續執行流。

Python,線程是搶占式的調度,多個線程并行執行時搶占共同的系統資源,而協程則是協同式的調度。其實Greenlet并非一種真正的并發機制,而是在同一線程內的不同函數的執行代碼塊之間進行切換,實施“你運行一會兒,我運行一會兒”,在進行切換時必須制定何時切換以及切換到哪兒。

進程與協程

進程與協程有什么異同點呢?

進程與協程都可以看作是一種執行流,執行流可以掛起,后續可以在掛起的位置恢復執行。

例如:在Linux的Shell中執行Hello程序

開始時Shell進程在運行并等待命令行的輸入,當執行Hello程序時,Shell通過系統調用來執行請求,此時系統調用會將控制權傳遞給操作系統,操作系統保存Shell進程的上下文并創建Hello進程以及上下文并將控制權交給Hello進程。當Hello進程終止后操作系統恢復Shell進程的上下文,并將控制權傳回給Shell進程,Shell進程繼續等待下一個命令的輸入。

73ccb425a710

python爬蟲教程。進程與協程相同點

當掛起一個執行流時,此時需要保存兩樣東西,其一是棧,其實在切換前局部變量以及函數的調用都需要保存否則將無法恢復,其二是寄存器狀態,寄存器狀態用于當執行流恢復后需要執行什么。寄存器和棧的結合可以理解為上下文,上下文切換的理解是CPU看上去像是在并行執行多個進程,這其實是通過CPU在進程間切換來實現的,操作系統實現這種交錯執行的機制稱為上下文切換。操作系統保存跟蹤進程運行所需的所有狀態信息,這種狀態就是上下文。在任意時刻操作系統只能執行一個進程代碼,當操作系統決定將控制權從當前進程轉移到某個進程時,就會進行上下文切換,也就是保存當前進程的上下文,并恢復新進程的上下文。然后將控制權傳遞給新進程,新進程從從它上次停止的地方開始。

進程與協程的不同點在于

執行流的調用者不同,進程是內核調度,而協程是用戶態調度,也就是說進程的上下文是在內核態中保存并恢復的,而協程是在用戶態保存恢復的,很顯然用戶態的代價要跟低。

進程會被強占,而協程不會。也就是說協程如果不主動讓出CPU,那么其他協程就沒有執行的機會。

對內存的占用不同,協程只需要4KB的棧空間就足夠了,而進程占用的內存要大的多。

python語言程序設計?從操作系統角度講,多協程的程序是單進程單協程的。

線程與協程

線程與協程有什么異同點呢?

協程也被稱為微線程,線程之間上下文切換的成本相對于協程而言是比較高的,尤其是開啟的線程較多的時候。而協程的切換成本則比較低。另外,同樣線程的切換更多的是依靠操作系統來控制,而協程的執行則是由用戶自己來控制。

計算機IO方式

根據計算機組成原理,計算機中IO的控制方式包括程序查詢方式、程序中斷方式、DMA方式、通道方式。目前計算機采用DMA和通道方式進行IO控制,這樣在進行IO操作時,CPU可以盡量不參與到這個過程中而去執行其它的操作。由于IO操作一般都比較耗時,采用DMA和通道方式可以將CPU從IO過程中解放出來,從而提高系統的效率。

python編程,在程序運行過程中一把會遇到兩種類型的IO,即當前機器的磁盤IO和網絡IO,這兩種IO操作一般會阻塞程序的執行,浪費CPU時間,因為此時程序分配到了時間片,在該時間片內程序是獨占CPU資源,由于IO被阻塞CPU沒有被其他程序享有進而被浪費。因此在編寫高性能程序時,IO是需要重點關注的。

目前可以通過如下途徑解決因IO帶來的效率問題:

減少IO次數,也就是優化程序的結構,將需要讀寫的數據匯集在一起進行一次性讀寫。

提高硬件的IO速度,比如使用SSD磁盤。

IO時不阻塞當前執行流,由DMA控制器或通道負責IO操作,CPU繼續執行程序的其他部分或執行其他程序。

大多涉及到IO的高性能庫一般都是通過第三種途徑解決IO的性能瓶頸,例如Tornado的異步操作,而Gevent正是基于Greenlet的協程。Gevent實現了Python標準庫中一些阻塞庫的非阻塞版本,如socket、os、select等,可以使用這些非阻塞的庫替代Python中阻塞的庫。

python和java,網絡IO模型

阻塞式單線程

最基本的IO模型,只有在處理完畢一個請求后才能處理下一個請求,缺點是效能差,如果有請求阻塞住,會讓服務無法繼續接受請求,但這種模型編寫代碼相對比較簡單,在應對訪問量不大的情況下非常適用。

阻塞式多線程

針對于單線程接受請求數量有限的缺點,一個很自然的想法是給每個請求開一個線程去處理,這樣做的好處是能夠接受更多的請求。缺點是當線程產生到一定數量之后,進程之間需要大量上下文切換的操作,此時會占用CPU大量的時間,不過這樣處理的話編寫代碼的難度稍高于單進程的情況。

非阻塞式事件驅動

python做什么的?為了解決多線程的問題,有一種做法是利用循環來檢查是否有網絡IO事件的發生,以便決定如何來進行處理,比如Reactor設計模式。這種做法的好處是進一步降低了CPU的資源消耗,缺點是這樣做會讓程序難以編寫,因為請求接受后的處理過程由Reactor來決定,使得程序的執行流程難以把握。當接收到一個請求后如果涉及到阻塞的操作,這個請求的處理過程會停下來去接受另一個請求,程序執行的流程不會像線性程序那樣直觀,比如Twisted框架就是采用此種模型的典型案例。

非阻塞式協程

非阻塞式協程是為了解決事件驅動模型執行流程不直觀的問題,在本質上也是事件驅動的,但加入了協程的概念。

Gevent

Gevent是一種基于協程的Python網絡庫,使用Greenlet提供并封裝了libevent事件循環的高層同步API,使開發者在不改變編程習慣的同時,以同步的方式編寫異步IO代碼。簡單來說,Gevent是基于libev和Greenlet的一個異步的Python框架。

libev是一個高性能的事件循環event loop實現。事件循環(IO多路復用)是解決阻塞問題實現并發的一種方式。事件循環event loop會捕獲并處理IO事件的變化,當遇到阻塞時就會跳出,當阻塞結束時則會繼續。這一點依賴于操作系統底層的select函數及其升級版poll和epoll。而Greenlet則是一個Python的協程管理和切換的模塊,通過Greenlet可以顯式地在不同的任務之間進行切換。

python getitem用法、Libev

Gevent的基本原理來自于libevent&libev,熟悉C語言的同學應該對這個lib不陌生。本質上libevent或者說libev都是一種事件驅動模型。這種模型對于提高CPU的運行效率,增強用戶的并發訪問非常有效。但因為其本身是一種事件機制,所以編寫起來有些繞,并不是很直觀。因此為了修正這個問題,有人引入了用戶態上下文切換機制,也就是說,如果代碼中引入了帶IO阻塞的代碼時,lib本身會自動完成上下文的切換,全程用戶都是沒有察覺的,這又是Gevent的由來。

Libev是高性能事件循環模型的網絡庫,包含大量新特性,是繼libevent之后的一套全新的網絡庫。libev追求的目標是速度更快、bug更少、特性更多、體積更小。和libevent類似,可以作為其替代者,提供更高的性能且無需復雜的配置。

libev機制提供了對指定文件描述符發生時調用回調函數的機制,libev是一個事件循環器,向libev注冊感興趣的事件,比如Socket可讀事件,libev會對所注冊的事件的源進行管理,并在事件發生時出發相應的程序。

Yield

Python對協程的支持是非常有限的,使用生成器generator中的yield可以一定程序上實現協程。比如傳統的生產者-消費者模型,即一個線程寫消息一個線程讀消息,通過鎖機制控制隊列和等待,但一不小心就可能出現死鎖。如果改用協程,生產者生產消息后直接通過yield跳轉到消費者并開始執行,等消費者執行完畢后再切換回生產者繼續生產,這樣做效率極高。

python length、$ vim test.py

#! /usr/bin/env python3

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

import time

def consumer():

r = ""

gevent python?while True:

n = yield r

if not n:

return

print("consumer %s"%n)

r = "200 OK"

python generator,def producer(c):

c.__next__()

n = 0

while n < 3:

n = n + 1

print("producer %s"%n)

python界面,r = c.send(n)

print("producer return %s\n"%r)

c.close()

if __name__ == "__main__":

c = consumer()

producer(c)

python 排序。$ python3 test.py

producer 1

consumer 1

producer return 200 OK

producer 2

consumer 2

python getitem,producer return 200 OK

producer 3

consumer 3

producer return 200 OK

代碼分析:首先調用c.__next__()啟動生成器,一旦生產出東西,則通過c.send(n)切換到消費者consumer來執行,消費者consumer通過yield獲取到消息后處理,然后通過yield將結果傳回。生產者producer獲取到消費者consumer處理的結果后繼續生產下一條消息。整個過程無鎖,由一個線程執行,生產者和消費者協作完成任務,所以稱之為協程。

Python通過yield提供了對協程的基本支持,但并不完全。而第三方的Gevent為Python提供了比較完善的協程支持,Gevent是第三方庫,可通過Greenlet實現協程。另外,Python中由于GIL的存在導致多線程一直不是很好用,相比之下,協程的優勢就更加突出了。

python應用,Greenlet

Greenlet是指使用一個任務調度器和一些生成器或協程實現協作式用戶空間多線程的一種偽并發機制,也就是所謂的微線程。Greenlet機制的主要思想是生成器函數或協程函數中的yield語句掛起函數的執行,直到稍后使用next()或send()操作進行恢復為止。可以使用一個調度器循環在一組生成器函數在將協作多個任務。

既然Gevent使用的是Greenlet,因此需要理解Greenlet的工作原理:每個協程都有一個parent,最頂層的協程是man thread或者是當前線程,每個協程遇到IO時會見控制權交給最頂層的協程,它會檢測到哪個協程的IO Event已經完成并將控制權交給它。

73ccb425a710

Greenlet

Greenlet的基本思路是:當一個Greenlet遇到IO操作時,比如訪問網絡時會自動切換到其它的Greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。由于IO操作非常耗時,經常會使程序處于等待狀態,有了Gevent自動切換協程,就保證總有Greenlet在運行,而不是等待IO。由于切換是在IO操作時自動完成,所以Gevent需要修改Python自帶的標準庫,這一過程在啟動時通過monkey patch猴子補丁來完成。

Swich

python gym?一個Greenlet是一個很小的獨立微線程,可以把它想象成一個堆棧幀,棧底是初始調用,棧頂是當前Greenlet的暫停位置,使用Greenlet創建一堆這樣的堆棧,然后在它們之間跳轉執行。跳轉并不是絕對的,因為一個Greenlet必須選擇跳轉到選擇好的另一個Greenlet,這會讓前一個掛起,而后一個恢復,兩個Greenlet之間的跳轉又被稱之為切換switch。當創建一個Greenlet時它會得到一個初始化過的空堆棧,當第一次切換到它時會啟動指定的函數,然后切換跳出Greenlet,當最終棧底函數結束時,Greenlet的堆棧又會變成空的,而Greenlet也就死掉了。當然,Greenlet也會因為一個未捕獲的異常而死掉。

Monkey-patching

Monkey-patching猴子補丁這個叫法源自于Zope框架,大家在修正Zope的Bug時經常會在程序后追加更新部分,這些被稱作“雜牌軍補丁(guerillapatch)”,后來guerilla逐漸寫成了gorllia(猩猩),再后來就寫成了monkey(猴子),所以猴子補丁的叫法就這么莫名其妙的得來了。之后在動態語言中,不改變源代碼而對功能進行追加和變更就統稱為“猴子補丁”。所以猴子補丁并不是Python中專有的,猴子補丁充分利用了動態語言的靈活性,可以對現有語言API進行追加、替換、修改,甚至性能優化等。使用猴子補丁的方式Gevent能夠修改標準庫中大部分的阻塞式系統調用,包括socket、ssl、threading、select等模塊,使其變為協作式運行。

Monkey-patching猴子補丁是將標準庫中大部分的阻塞式調用替換成非阻塞的方式,包括socket、ssl、threading、select、httplib等。通過monkey.path_xxx()函數來打補丁,根據Gevent文檔中的建議,應當將猴子補丁的代碼盡可能早的被調用,這樣可以避免一些奇怪的異常。

使用Gevent的性能要比傳統的線程高,但不得不說的一個坑是如果使用Monkey-patching猴子補丁,Gevent將直接修改標準庫中大部分的阻塞式調用,包括socket、ssl、threading、select等模塊,而變為協作式運行。但無法保證在復雜的生產環境中那些地方使用標準庫因補丁而出現的奇怪問題。另外是第三方庫的支持,需要確保項目中使用到的其他網絡庫也必須使用純Python或明確支持Gevent。

Gevent應該在什么場景中使用呢?

pythonget。Gevent的優勢在于可以通過同步的邏輯實現并發操作,大大降低編寫并行或并發程序的難度。另外,在一個進程中使用Gevent可以有效地避免對臨界資源的互斥訪問。如果程序中涉及到較多的IO,可以使用Gevent替代多線程來提高程序的效率,但是由于Gevent中的協程的調度是由使用者而非操作系統決定的,Gevent主要解決的問題是IO問題,通過提高IO-bound類型的程序的效率,另外由于是在一個進程中實現協程,而操作性i同是以進程為單位分配處理資源的(一個進程分配一個處理機)。因此,Gevent并不適合對任務延遲有要求的場景,比如交互式程序中。也不適用于CPU-bound類型的任務和需要使用多處理機的場景(通過運行多個進程,每個進程內實現協程來解決這個問題。)。

安裝使用

Ubuntu系統下可通過apt-get安裝gevent庫

$ sudo apt-get install python-gevent

如果使用的Python3的版本,安裝如下:

$ sudo apt-get install python3-gevent

也可以直接使用Python的包管理工具pip命令進行安裝,不過需要注意版本與權限。

$ pip install gevent

入門案例:使用Gevent控制程序執行順序

$ vim test.py

#! /usr/bin/env python3

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

import gevent

from gevent import monkey

monkey.patch_socket()

def fn(n):

for i in range(n):

print(gevent.getcurrent(), i)

# 創建協程對象

greenlet1 = gevent.spawn(fn, 3)

greenlet2 = gevent.spawn(fn, 3)

# 等待greenlet1執行結束

greenlet1.join()

greenlet2.join()

# 獲取fn的返回值

# print(greenlet1.value)

$ python3 test.py

0

1

2

0

1

2

根據執行結果可知:greenlet是依次運行而不是交替運行的,如果要讓greenlet交替運行則需要通過gevent.sleep()交出控制權。

green.spawn會啟動所有協程,協程都是運行在同一個線程之中的,所以協程不能夠跨線程同步數據。

$ vim test.py

#! /usr/bin/env python3

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

import gevent

from gevent import monkey

monkey.patch_socket()

def fn(n):

for i in range(n):

print(gevent.getcurrent(), i)

gevent.sleep(0)

greenlet1 = gevent.spawn(fn, 3)

greenlet2 = gevent.spawn(fn, 3)

# 合并兩步操作

gevent.joinall([greenlet1, greenlet2])

$ python3 test.py

0

0

1

1

2

2

程序的重要部分是將任務函數封裝到gevent.spawn中,初始化的Greenlet列表存放在數組線程中,此數組會被傳給gevent.joinall函數,gevent.joinall函數會阻塞當前流程,并執行所有給定的Greenlet,執行流只會在所有的Greenlet執行完畢后才會繼續向下執行。

在Gevent中gevent.sleep()模擬的是Gevent可以識別的IO阻塞,若使用time.sleep()或其他阻塞,Gevent是不能夠直接識別的,需要使用猴子補丁,注意猴子補丁必須放在被打補丁的前面,如time、socket模塊之前。

#! /usr/bin/env python3

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

import gevent

from gevent import monkey

monkey.patch_all()

import time

def fn(n):

for i in range(n):

print(gevent.getcurrent(), i)

time.sleep(1)

greenlet1 = gevent.spawn(fn, 3)

greenlet2 = gevent.spawn(fn, 3)

gevent.joinall([greenlet1, greenlet2])

案例:Greenlet模塊內部使用了協程的概念,在單線程內需要手動調用switch函數切換協程。

$ vim test.py

#! /usr/bin/env python3

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

from greenlet import greenlet

def eat(name):

print("%s eat 1"%name)

g2.switch("egon")

print("%s eat 2"%name)

g2.switch()

def play(name):

print("%s play 1"%name)

g1.switch()

print("%s play 2"%name)

g1 = greenlet(eat)

g2 = greenlet(play)

g1.switch("egon")

$ python3 test.py

egon eat 1

egon play 1

egon eat 2

egon play 2

根據上述代碼可以發現,協程一旦遇到IO操作就會自動切換到其它協程,使用yield是無法實現的。

例如:

$ vim test.py

#! /usr/bin/env python3

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

import gevent

from gevent import socket

urls = [

"www.baidu.com",

"www.python.org",

"www.example.com"

]

jobs = [

gevent.spawn(socket.gethostbyname, url) for url in urls

]

gevent.joinall(jobs, timeout=2)

result = [

job.value for job in jobs

]

print(result)

$ python3 test.py

['112.80.248.75', '151.101.108.223', '93.184.216.34']

使用gevent.spawn函數spawn引發一些任務jobs,再通過gevent.joinall將所有任務jobs加入到為協程執行隊列中等待其完成,同時設置超時時間為2秒。執行后的結果通過檢查gevent.greenlet.value值來收集。

gevent.socket.gethostbyname函數與標準的socket.gethostbyname有著相同的接口,但不會阻塞整個解釋器,因此會使其他Greenlet跟隨著無阻塞的請求而執行。

猴子補丁

from gevent import monkey

patch_all

patch_all(

socket = True,

dns = True,

time = True,

select = True,

thread = True,

os = True,

ssl = True,

httplib = False,

subprocess = True,

sys = False,

aggressive = True,

Event = False,

builtins = True,

signal = True

)

Gevent的Monkey可以為socket、dns、time、select、thread、os、ssl、httplib、subprocess、sys、aggressive、Event、builtins、signal模塊打上的補丁,打上補丁后他們就是非阻塞的了。

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/182789.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息