A brief discussion on the cooperative process of “swoole”

Time:2021-4-28

Reading this article requires the following knowledge points

  • Understand the process, thread related foundation
  • Proficient PHP output of Hello World
  • Can spell the word “spool”

Introduction of Xiecheng

What is Xiecheng?

A coroutine is a function that can suspend its execution (yield) until the given given YieldInstruction finishes.

To put it simply, a coroutine is a kind of light-weight thread that is implemented by a programmer under a thread and cooperates with a lighter concurrent thread

With the increase of the number of programmers, the big guys are also growing explosively. Of course, some people begin to think that threads are not easy to use. What should we do? Of course, it is based on the concept of thread to realize a lighter and better set of lightweight threads (in fact, a coroutine can not be regarded as a thread, because a thread can have multiple coroutines)
A brief discussion on the cooperative process of

Differences between coroutines and threads

essence

Thread kernel state
Cooperative user mode

Dispatching mode

The thread scheduling method isSystem schedulingThe common scheduling strategies areTime sharing schedulingpreemptive scheduling . In other words, the scheduling of threads is completely out of our control

The scheduling mode of the cooperative process is as followsCooperative schedulingIt is not controlled by the kernel and is scheduled by free policy

wait

Collaborative scheduling?
A brief discussion on the cooperative process of

As mentioned above, the coroutine is in user mode, so the so-called cooperative scheduling can be directly understood as the scheduling mode written by the programmer, that is, I can schedule as I want, instead of being scheduled through the system kernel.

Deep….Understanding the synergetic process of spool

Since we want to understand the coroutine of spool, we must know the coroutine model of spool.
The coroutine of spool is based on single thread. It can be understood that the switch of coroutine is serial, and only one coroutine runs at the same time

At this point, someone must have asked. Go, go’s coroutine is based on multithreading. Of course, each has its own advantages, you can use the search engine to understand

We can directly copy & paste the following code, and then demo run in the local environment

<?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);    

The results are as follows

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

Surely someone will think, wow, it’s finished in 2 seconds, it’s not blocked at all!!
A brief discussion on the cooperative process of
Well, actually, we can go back to the concept of the next coroutine when we finish the execution in 2 seconds.
What we can focus on is the execution sequence. 1 and 7 are the execution of non cooperative processes, and the results can immediately return to meet the expectations.
On the scheduling order of coprocesses
Why26543no65432perhaps23456What about orderly return

In order to find our answer, we can only know something through the source code
A brief discussion on the cooperative process of

Analysis of source code

A brief discussion on the cooperative process of

Picture from https://segmentfault.com/a/11…

If there is no strong foundation, there is a gnawed APUE premise (of course, I do not! T_ T)
We need to care about the following two
yieldSwitching coroutine
resumeRecovery process
A brief discussion on the cooperative process of

Establishment of collaborative process

<?php
go (function(){
Echo "spoole is great";
});

CalledswoolePackage toPHPOfgoFunction to create a coroutine for

We expand the source code according to the

Most of themPHPThe parameter declarations of extension functions and methods are placed in theswoole_*.ccswoole.ccInside.

PHP_FALIAS(go, swoole_coroutine_create, arginfo_swoole_coroutine_create)

You can see go > spool_ coroutine_ create

In swoole_ Found in the coroutine.cc file

PHP_FUNCTION(swoole_coroutine_create)
{
    ....
    //Key points should be examined
    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))
    {
        //Key points should be examined
        activate();
    }

    //Save callback function
    php_coro_args php_coro_args;
    //Function information
    php_coro_args.fci_cache = fci_cache;
    //Parameters
    php_coro_args.argv = argv;
    php_coro_args.argc = argc;
    //Key points should be examined
    save_task(get_task());

    //Key points should be examined
    return Coroutine::create(main_func, (void*) &php_coro_args);
}
//Save stack 
void PHPCoroutine::save_task(php_coro_task *task)
{
    save_vm_stack(task);
    save_og(task);
}
//Initialize the event of 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;
}

Continue to jump down according to coroutine:: create

    static inline long create(coroutine_func_t fn, void* args = nullptr)
    {
        return (new Coroutine(fn, args))->run();
    }

Execute immediately after creating the coroutine
Let’s look at the construction method

    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();
        }
    }

In the above code, I can see that there is also a class of context. We can guess that it does three things

  1. Assign the corresponding coroutine ID (each coroutine has its own ID)
  2. Save context
  3. Update the current number of coroutines

swooleThe library used isboost.contextSelf Searching
The main exposed function interfaces arejump_fcontextandmake_fcontext
Specific roleSaves the context of the current execution statePauses the current execution stateEnough to jump to another location to continue

After the establishment, the project will be executed immediately

inline long run()
    {
        long cid = this->cid;
        origin = current;
        current = this;
        //Depending on boost.context to cut stack
        ctx.swap_in();
        //Judge whether the execution is finished
        check_end();
        return cid;
    }

Judge whether to end or not

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);
        }
    }

According to ctx.is_ End() function found

    inline bool is_end()
    {
        return end_;
    }
bool Context::swap_in()
{
    jump_fcontext(&swap_ctx_, ctx_, (intptr_t) this, true);
    return true;
}

We can summarize the main things that swoole does when creating a coroutine

  1. Testing environment
  2. Analytic parameters
  3. Save context
  4. Switch C stack
  5. Implementation of collaborative process

Yield of coprocesses

We use the demo above\Swoole\Coroutine::sleep(2)
According to the above statement, we use theswoole_corotunine_system.ccIt is found that the corresponding function isswoole_coroutine_systemOfsleep

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;
}

CalledsleepFunction and then do three things for the current coroutine
1. Increasedtimertimer
2. After the registration of the fallback function is delayedresumeXiecheng
3. AdoptionyieldRelinquish dispatch

int System::sleep(double sec)
{
//Get the current coroutine
    Coroutine* co = Coroutine::get_current_safe();
   //swTimer_ Add register timer sleep_ Function of timeout callback
   if (swTimer_add(&SwooleG.timer, (long) (sec * 1000), 0, co, sleep_timeout) == NULL)
    {
        return -1;
    }
    //Give up the current CPU
    co->yield();
    return 0;
}

//Callback function
static void sleep_timeout(swTimer *timer, swTimer_node *tnode)
{
   //Resume scheduling
    ((Coroutine *) tnode->data)->resume();
}
swTimer_node* swTimer_add(swTimer *timer, long _msec, int interval, void *data, swTimerCallback callback)
{
    ....
    //Save the current context and the corresponding expiration time
    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 saves the fastest expired events
    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;
}

Switching of coroutines

We

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);
    }
    //Save the current coroutine as origin > previous
    origin = current;
    //The coroutine to be executed becomes current
    current = this;
    //Stack execution
    ctx.swap_in();
    check_end();
}

By this time, the answer to the call order of the coroutine has come out
A brief discussion on the cooperative process of

When creating a coroutine(new Coroutine(fn, args))->run();andsleeptriggeryieldIt’s all changingCorotunineOfcurrentandoriginWhen executing the switch again, it alternates with the time when PHP code creates the coroutine, instead of what we imaginedStackorqueueOrderly implementation
For example, when there are only two co programs created

<?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);

The output is not disturbed because of the small execution time of the continuous creation process

php swoole_go_demo1.php
index:1
is corotunine:0
index:2
is corotunine:1
index:3
is corotunine:1

When creating 200 coroutines in succession
It’s chaotic to returnindexIn line with the expected conjecture

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

Last egg

We use go’s coroutine to implement the above demo

A brief discussion on the cooperative process of

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)
}

Guess what the return result is. You can study it according to go’s coroutine based on multithreading
A brief discussion on the cooperative process of

At the end of the article, I only understand it according to the code and data. If I have any mistakes, I feel very sorry. If I am misled by some wrong ideas, I can only say
A brief discussion on the cooperative process of