日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python 协程原理_Python协程greenlet实现原理

發布時間:2025/1/21 python 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python 协程原理_Python协程greenlet实现原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

greenlet是stackless

Python中剝離出來的一個項目,可以作為官方CPython的一個擴展來使用,從而支持Python協程。gevent正是基于greenlet實現。

協程實現原理

實現協程主要是在協程切換時,將協程當前的執行上下文保存到協程關聯的context中。在c/c++這種native程序中實現協程,需要將棧內容和CPU各個寄存器的內容保存起來。在Python這種VM中則有些不同。例如,在以下基于greenlet協程的python程序中:

1

2

3

4

5

6

7

8

9

10

11

12

13def foo():bar()def bar():a = 3 + 1gr2.switch()def func():passgr1 = greenlet(foo)gr2 = greenlet(func)gr1.switch()

在bar中gr2.switch切換到gr2時,協程庫需要保存gr1協程的執行上下文。這個上下文包括:

Python VM的stack

Python VM中解釋執行的上下文

理解以上兩點非常重要,至于為什么呢?想象一下如何去實現一個Python

VM,去解釋執行一段Python代碼。其實這在任何基于VM的語言中,原理都是一樣的(native程序可以把x86物理CPU也視作特殊的VM)。可以參考Python解釋器簡介-深入主循環。主要包含兩方面內容:

VM在執行代碼時,其自身調用棧通常都是遞歸的

VM在執行代碼時,通常會創建相應的數據結構來表示代碼執行塊,例如通常會有個struct Frame來表示一個函數

在VM的實現中通常會有類似以下的代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16struct Frame {unsigned char *codes; // 存放代碼指令size_t pc; // 當前執行的指令位置int *stack; // stack-based的VM會有一個棧用于存放指令操作數};void op_call(frame) {switch (OP_CODE()) {case OP_CALL:child_frame = new_frame()op_call(child_frame)...case OP_ADD:op_add(...)}}

對應到前面的Python例子代碼,在某一時刻VM的call stack可能是這樣的:

1

2

3op_addop_callop_call

理解了以上內容后,就可以推測出greenlet本質上也是做了以上兩件事。

greenlet實現原理

greenlet庫中每一個協程稱為一個greenlet。greenlet都有一個棧空間,如下圖:

圖中未表達出來的,greenlet的棧空間地址可能是重疊的。對于活躍的(當前正在運行)的greenlet,其棧內容必然在c程序棧頂。而不活躍的被切走的greenlet,其棧內容會被copy到新分配的堆內存中。greenlet的棧空間是動態的,其起始地址是固定的,但棧頂地址不固定。以下代碼展示一個greenlet的棧空間如何確定:

1

2

3

4579 if (!PyGreenlet_STARTED(target)) { // greenlet未啟動,是一個需要新創建的greenlet580 void* dummymarker; // 該局部變量的地址成為新的greenlet的棧底581 ts_target = target;582 err = g_initialstub(&dummymarker); // 創建該greenlet并運行

以上greenlet->stack_stop確定了棧底,而棧頂則是動態的,在切換到其他greenlet前,對當前greenlet進行上下文的保存時,獲取當前的RSP(程序實際運行的棧頂地址):

1

2

3

4

5

6

7

8

9

10410 static int GREENLET_NOINLINE(slp_save_state)(char* stackref)411 {412 /* must free all the C stack up to target_stop */413 char* target_stop = ts_target->stack_stop;414 PyGreenlet* owner = ts_current;415 assert(owner->stack_saved == 0);416 if (owner->stack_start == NULL)417 owner = owner->stack_prev; /* not saved if dying */418 else419 owner->stack_start = stackref; // stack_start指向棧頂

stackref是通過匯編獲取當前RSP寄存器的值:

1__asm__ ("movl %%esp, %0" : "=g" (stackref));

保存棧內容到堆內存參看g_save的實現,沒什么特別的。除了保存棧內容外,如上一節講的,還需要保存VM執行函數所對應的Frame對象,這個在g_switchstack中體現:

1

2

3

4

5

6

7

8

9460 PyThreadState* tstate = PyThreadState_GET(); // 獲取當前線程的VM執行上下文461 current->recursion_depth = tstate->recursion_depth;462 current->top_frame = tstate->frame; // 保存當前正在執行的frame到當前正在執行的greenlet...473 slp_switch(); // 做棧切換...487 PyThreadState* tstate = PyThreadState_GET();488 tstate->recursion_depth = target->recursion_depth;489 tstate->frame = target->top_frame; // 切換回來

上面的代碼展示VM frame的切換。接下來看下最復雜的部分,當切換到目標greenlet時,如何恢復目標greenlet的執行上下文,這里主要就是恢復目標greenlet的棧空間。假設有如下greenlet應用代碼:

1

2

3

4

5

6

7

8

9def test1():gr2.switch()def test2():print('test2')gr1 = greenlet(test1)gr2 = greenlet(test2)gr1.switch()

在gr1中切換到gr2時,也就是gr2.switch,會發生什么事情。

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

33// g_switch 實現574 if (PyGreenlet_ACTIVE(target)) {575 ts_target = target; // 找到目標greenlet,也就是gr2576 err = g_switchstack(); // 開始切換// g_switchstack 實現462 current->top_frame = tstate->frame;...473 err = slp_switch();// slp_switch 實現,根據不同平臺實現方式不同,原理相同69 SLP_SAVE_STATE(stackref, stsizediff);// 這個很重要,強行將當前的棧指針ESP/EBP (32位OS)通過加上一個與目標greenlet棧地址的偏移,而回到了// 目標greenlet的棧空間。可以在下文看到stsizediff的獲取實現70 __asm__ volatile (71 "addl %0, %%esp\n"72 "addl %0, %%ebp\n"73 :74 : "r" (stsizediff)75 );76 SLP_RESTORE_STATE();// SLP_SAVE_STATE 實現316 #define SLP_SAVE_STATE(stackref, stsizediff) \317 stackref += STACK_MAGIC; \318 if (slp_save_state((char*)stackref)) return -1; \319 if (!PyGreenlet_ACTIVE(ts_target)) return 1; \// 獲取目標greenlet的棧空間與當前棧地址的偏移,用于稍后設置當前棧地址回目標greenlet的棧地址320 stsizediff = ts_target->stack_start - (char*)stackref// slp_save_state 沒啥看的,前面也提過了,主要就是復制當前greenlet棧內容到堆內存// SLP_RESTORE_STATE 也沒什么看的,主要就是把greenlet堆內存復制回棧空間

以上,首先將ESP/EBP的值改回目標greenlet當初切換走時的ESP/EBP值,然后再把greenlet的棧空間內存(存放于堆內存中)全部復制回來,就實現了greenlet棧的回切。尤其注意的是,這個棧中是保存了各種函數的return地址的,所以當slp_switch返回時,就完全恢復到了目標greenlet當初被切走時棧上的內容,包括各種函數調用棧。而當前greenlet的棧,則停留在了類似以下的函數調用棧:

1

2

3g_switchstackg_switch...

參考

總結

以上是生活随笔為你收集整理的python 协程原理_Python协程greenlet实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。