閱讀本文須要如下知識點php
協程是什麼?node
A coroutine is a function that can suspend its execution (yield) until the given given YieldInstruction finishes.
簡單的說協程是寄宿在線程下程序員實現的一種跟更輕量的併發的協做輕量線程react
隨着程序員人羣的增大,大佬也不斷的爆發式增加,固然就開始有人以爲線程很差用了,那怎麼辦呢?固然是基於線程的理念上再去實現一套更加輕量、更好騙star的一套輕量線程(事實上協程不能徹底被認爲線程,由於一個線程能夠有多個協程)
程序員
線程 內核態
協程 用戶態segmentfault
線程的調度方式爲系統調度,經常使用的調度策略有分時調度、搶佔調度。說白就是線程的調度徹底不受本身控制swoole
協程的調度方式爲協做式調度 不受內核控制由自由策略調度切換多線程
等等併發
協做式調度?
函數
上述說了協程是用戶態的,因此所謂的協做式調度直接能夠理解爲是程序員寫的調度方式,也就是我想怎麼調度就怎麼調度,而不用經過系統內核被調度。ui
既然打算淺入理解的swoole的協程,咱們必需要知道swoole的協程模型。
swoole的協程是基於單線程。能夠理解爲協程的切換是串行的,再同一個時間點只運行一個協程.
說到這裏,確定就有人問了。go呢,go的協程的是基於多線程。固然各有各的好處,具體能夠自行使用搜索引擎瞭解
咱們能夠直接copy & paste 下面代碼,再本地的環境進行的 demo run
<?php $func = function ($index, $isCorotunine = true) { $isCorotunine && \Swoole\Coroutine::sleep(2); echo "index:" . $index . PHP_EOL; echo "is corotunine:" . intval($isCorotunine) . PHP_EOL; }; $func(1, false); go($func, 2, true); go($func, 3, true); go($func, 4, true); go($func, 5, true); go($func, 6, true); $func(7, false);
會獲得如下結果
index:1 is corotunine:0 index:7 is corotunine:0 index:2 is corotunine:1 index:6 is corotunine:1 index:5 is corotunine:1 index:4 is corotunine:1 index:3 is corotunine:1
確定有人會想,哇塞,盡然2秒都執行完了,一點都不堵塞啊!!
好了,事實上關於2秒執行完的事情能夠回過頭再去看下協程的概念。
咱們能夠關注的是執行順序,1和7是非協程的執行能立馬返回結果符合預期。
關於協程的調度順序
爲何是26543不是65432或者23456有序的返回呢
爲了找到咱們的答案,咱們只能經過源碼進行知曉一些東西
圖來自 https://segmentfault.com/a/11...
若是沒有較強的基礎還有啃爛的apue的前提下(固然我也沒有!T_T)
咱們須要關心的是如下兩個
yield 切換協程
resume 恢復協程
<?php go (function(){ echo "swoole 太棒了"; });
調用的swoole
封裝給PHP
的go
函數爲建立一個協程
咱們根據拓展源碼中的
大部分的PHP
擴展函數以及擴展方法的參數聲明放在swoole_*.cc
、swoole.cc
裏面。
PHP_FALIAS(go, swoole_coroutine_create, arginfo_swoole_coroutine_create)
能夠知道 go->swoole_coroutine_create
在swoole_coroutine.cc文件裏找到
PHP_FUNCTION(swoole_coroutine_create) { .... // 劃重點 要考 long cid = PHPCoroutine::create(&fci_cache, fci.param_count, fci.params); .... } long PHPCoroutine::create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv) { if (sw_unlikely(Coroutine::count() >= config.max_num)) { php_swoole_fatal_error(E_WARNING, "exceed max number of coroutine %zu", (uintmax_t) Coroutine::count()); return SW_CORO_ERR_LIMIT; } if (sw_unlikely(!active)) { // 劃重點 要考 activate(); } // 保存回調函數 php_coro_args php_coro_args; //函數信息 php_coro_args.fci_cache = fci_cache; //參數 php_coro_args.argv = argv; php_coro_args.argc = argc; // 劃重點 要考 save_task(get_task()); // 劃重點 要考 return Coroutine::create(main_func, (void*) &php_coro_args); }
// 保存棧 void PHPCoroutine::save_task(php_coro_task *task) { save_vm_stack(task); save_og(task); }
// 初始化reactor的事件 inline void PHPCoroutine::activate() { if (sw_unlikely(active)) { return; } /* init reactor and register event wait */ php_swoole_check_reactor(); /* replace interrupt function */ orig_interrupt_function = zend_interrupt_function; zend_interrupt_function = coro_interrupt_function; /* replace the error function to save execute_data */ orig_error_function = zend_error_cb; zend_error_cb = error; if (config.hook_flags) { enable_hook(config.hook_flags); } if (SWOOLE_G(enable_preemptive_scheduler) || config.enable_preemptive_scheduler) { /* create a thread to interrupt the coroutine that takes up too much time */ interrupt_thread_start(); } if (!coro_global_active) { if (zend_hash_str_find_ptr(&module_registry, ZEND_STRL("xdebug"))) { php_swoole_fatal_error(E_WARNING, "Using Xdebug in coroutines is extremely dangerous, please notice that it may lead to coredump!"); } /* replace functions that can not work correctly in coroutine */ inject_function(); coro_global_active = true; } /** * deactivate when reactor free. */ swReactor_add_destroy_callback(SwooleG.main_reactor, deactivate, nullptr); active = true; }
根據Coroutine::create繼續往下跳轉
static inline long create(coroutine_func_t fn, void* args = nullptr) { return (new Coroutine(fn, args))->run(); }
在建立完協程後立馬執行
咱們觀察下構造方法
Coroutine(coroutine_func_t fn, void *private_data) : ctx(stack_size, fn, private_data) { cid = ++last_cid; coroutines[cid] = this; if (sw_unlikely(count() > peak_num)) { peak_num = count(); } }
上述代碼我能夠發現還有一個Context的類 這個構造函數咱們能夠猜到作了3件事情
swoole
使用的協程庫爲boost.context
可自行搜索
主要暴露的函數接口爲jump_fcontext
和make_fcontext
具體的做用保存當前執行狀態的上下文
、暫停當前的執行狀態
、夠跳轉到其餘位置繼續執行
建立完協程立馬執行
inline long run() { long cid = this->cid; origin = current; current = this; // 依賴boost.context 切棧 ctx.swap_in(); // 判斷是否執行結束 check_end(); return cid; }
判斷是否結束
inline void check_end() { if (ctx.is_end()) { close(); } else if (sw_unlikely(on_bailout)) { SW_ASSERT(current == nullptr); on_bailout(); // expect that never here exit(1); } }
根據ctx.is_end()的函數找到
inline bool is_end() { return end_; }
bool Context::swap_in() { jump_fcontext(&swap_ctx_, ctx_, (intptr_t) this, true); return true; }
咱們能夠總結下swoole在建立協程的時候主要作了哪些事情
上述的demo咱們使用\Swoole\Coroutine::sleep(2)
根據上述說函數申明的咱們在swoole_corotunine_system.cc
發現對應函數爲swoole_coroutine_system
的sleep
PHP_METHOD(swoole_coroutine_system, sleep) { double seconds; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_DOUBLE(seconds) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (UNEXPECTED(seconds < SW_TIMER_MIN_SEC)) { php_swoole_fatal_error(E_WARNING, "Timer must be greater than or equal to " ZEND_TOSTR(SW_TIMER_MIN_SEC)); RETURN_FALSE; } System::sleep(seconds); RETURN_TRUE; }
調用了sleep
函數以後對當前的協程作了三件事
1.增長了timer
定時器
2.註冊回掉函數再延遲以後resume
協程
3.經過yield
讓出調度
int System::sleep(double sec) { // 獲取當前的協程 Coroutine* co = Coroutine::get_current_safe(); //swTimer_add 註冊定時器 sleep_timeout回調的函數 if (swTimer_add(&SwooleG.timer, (long) (sec * 1000), 0, co, sleep_timeout) == NULL) { return -1; } // 讓出當前cpu co->yield(); return 0; } // 回調函數 static void sleep_timeout(swTimer *timer, swTimer_node *tnode) { // 恢復調度 ((Coroutine *) tnode->data)->resume(); }
swTimer_node* swTimer_add(swTimer *timer, long _msec, int interval, void *data, swTimerCallback callback) { .... // 保存當前上下文和對應過時時間 tnode->data = data; tnode->type = SW_TIMER_TYPE_KERNEL; tnode->exec_msec = now_msec + _msec; tnode->interval = interval ? _msec : 0; tnode->removed = 0; tnode->callback = callback; tnode->round = timer->round; tnode->dtor = NULL; // _next_msec保存最快過時的事件 if (timer->_next_msec < 0 || timer->_next_msec > _msec) { timer->set(timer, _msec); timer->_next_msec = _msec; } tnode->id = timer->_next_id++; if (sw_unlikely(tnode->id < 0)) { tnode->id = 1; timer->_next_id = 2; } tnode->heap_node = swHeap_push(timer->heap, tnode->exec_msec, tnode); .... timer->num++; return tnode; }
咱們
void Coroutine::resume() { SW_ASSERT(current != this); if (sw_unlikely(on_bailout)) { return; } state = SW_CORO_RUNNING; if (sw_likely(on_resume)) { on_resume(task); } // 將當前的協程保存爲origin -> 理解程previous origin = current; // 須要執行的協程 變成 current current = this; // 入棧執行 ctx.swap_in(); check_end(); }
到這裏時候 關於協程調用順序的答案已經出來了
在建立協程的時候(new Coroutine(fn, args))->run();
和sleep
觸發yield
都在不斷變動的Corotunine
的current
和origin
再執切換的時候和php代碼建立協程的時間發生穿插,而不是咱們想象中的棧
或隊列
有序執行
好比當建立協程只有2個的時候
<?php $func = function ($index, $isCorotunine = true) { $isCorotunine && \Swoole\Coroutine::sleep(2); echo "index:" . $index . PHP_EOL; echo "is corotunine:" . intval($isCorotunine) . PHP_EOL; }; $func(1, false); go($func, 2, true); go($func, 3, true);
返回輸出 由於連續建立協程的執行時間小沒有被打亂
php swoole_go_demo1.php index:1 is corotunine:0 index:2 is corotunine:1 index:3 is corotunine:1
當連續建立的時候200個協程的時候
返回就變得打亂的index
符合預計猜測
index:1,index:2,index:4,index:8,index:16,index:32,index:64,index:128,index:129,index:65,index:130,index:131,index:33,index:66,index:132,index:133,index:67,index:134,index:135,index:17,index:34,index:68,index:136,index:137,index:69,index:138,index:139,index:35,index:70,index:140,index:141,index:71,index:142,index:143,index:9,index:18,index:36,index:72,index:144,index:145,index:73,index:146,index:147,index:158,index:157,index:156,index:155,index:154,index:153,index:152,index:151,index:37,index:74,index:148,index:149,index:75,index:150,index:19,index:38,index:76,index:77,index:39,index:78,index:79,index:5,index:10,index:20,index:40,index:80,index:81,index:41,index:82,index:83,index:21,index:127,index:126,index:125,index:124,index:123,index:122,index:121,index:120,index:119,index:118,index:117,index:116,index:115,index:114,index:113,index:112,index:111,index:110,index:109,index:108,index:107,index:106,index:105,index:104,index:103,index:102,index:101,index:100,index:99,index:98,index:97,index:96,index:95,index:94,index:93,index:92,index:91,index:90,index:89,index:88,index:87,index:42,index:84,index:85,index:43,index:86,index:11,index:22,index:44,index:45,index:23,index:46,index:47,index:3,index:6,index:12,index:24,index:48,index:49,index:25,index:50,index:51,index:13,index:26,index:63,index:62,index:61,index:60,index:59,index:58,index:57,index:56,index:55,index:52,index:53,index:27,index:54,index:7,index:14,index:28,index:29,index:15,index:30,index:31,index:200,index:199,index:192,index:185,index:175,index:168,index:161,index:163,index:172,index:179,index:187,index:194,index:174,index:160,index:173,index:176,index:198,index:195,index:180,index:167,index:169,index:184,index:197,index:193,index:177,index:162,index:171,index:186,index:182,index:164,index:191,index:183,index:166,index:196,index:178,index:170,index:189,index:188,index:165,index:181,index:190,index:159
咱們使用GO的協程的來實現上述的demo
package main import ( "fmt" "time" ) var count int = 0 func main() { output(false, 1) go output(true, 2) go output(true, 3) go output(true, 4) go output(true, 5) go output(true, 6) output(false, 7) time.Sleep(time.Second) } func output(isCorotunine bool, index int) { time.Sleep(time.Second) count = count + 1 fmt.Println(count, isCorotunine, index) }
猜猜返回結果是如何的 能夠根據go的協程基於多線程的方式再去研究下
寫給最後,文章純屬本身根據代碼和資料理解,若是有錯誤麻煩提出來,倍感萬分,若是由於一些錯誤的觀點被誤導我只能說