Process, thread, coroutine

What is the process? A process is a startup instance of an application. For example, when you start a piece of software, you start a process. Processes have code and open file resources, data resources, and separate memory space.

What is a thread? A thread belongs to a process and is the executor of a program. A process contains at least one main thread and may have more child threads. There are two scheduling strategies for threads, one is time-sharing scheduling and the other is preemptive scheduling.

What is a coroutine? Coroutines are lightweight threads. The creation, switching, suspension, and destruction of coroutines are all memory operations with very low consumption. Coroutines belong to threads, and they are executed in threads. The scheduling of coroutines is manually switched by users, so it is also called user-space threads. 3 The scheduling strategy of coroutines is: cooperative scheduling.

Most of the current mainstream languages have chosen multithreading as a concurrency facility, and the concept related to threads is Preemptive multitasking, while the concept related to coroutines is cooperative multitasking.

In fact, every time a process or thread is blocked or switched, it needs to be caught in a system call. The CPU first runs the operating system’s scheduler, and then the scheduler decides which process (thread) to run.

And because of the indeterminate nature of preemptive scheduling, synchronization needs to be handled very carefully with threads, whereas coroutines do not have this problem at all (event driven and asynchronous programs have the same advantage).

Because the coroutine is the user to write their own scheduling logic, for our CPU, coroutine is actually a single thread, so the CPU does not have to consider how to schedule, switch context, which eliminates the CPU switching overhead, so coroutine is better than multithreading to a certain extent.

Advantages of coroutines over multithreading Multithreading programming is difficult because the scheduler can interrupt the thread at any time. You must remember to retain locks to protect important parts of the program from multithreading breaking during execution.

By default, coroutines are fully protected against interrupts. We must display output for the rest of the program to run. In the case of coroutines, there is no need to keep the lock, and if you operate synchronously between multiple threads, the coroutine itself will be synchronized, because only one coroutine is running at any one time.

Summarize the following points:

  • No system kernel context switch, reduce overhead;
  • No overhead of atomic operation locking and synchronization, no worry about resource sharing;
  • Single thread can achieve high concurrency, single core CPU even support tens of thousands of coroutines is not a problem,

Therefore, it is suitable for high concurrency processing, especially in the application of web crawler.

Swoole coroutine

Swoole’s coroutine client must be used in the context of the coroutine.

// The first case: $server->on('Request', function($Request, $Mysql = new Swoole\Coroutine\ Mysql (); $mysql->connect([]); $mysql->query(); }); // The second case: $server->on('WorkerStart', function() {$server->on('WorkerStart', function() { Go (function(){$Mysql = new Swoole\Coroutine\ Mysql (); $mysql->connect([]); $mysql->query(); }); });Copy the code

Swoole’s coroutines are single-threaded and cannot take advantage of multi-core cpus, with only one scheduled at a time.

$n = 4; for ($i = 0; $i < $n; {$i++) go (function () use ($I) {/ / simulation IO wait Co: : sleep (1); echo microtime(true) . ": hello $i " . PHP_EOL; }); }; echo "hello main \n"; $PHP test. PHP hello main 1558749158.0913: hello 0 1558749158.0915: hello 3 1558749158.0915: Hello 2 1558749158.0915: Hello 1Copy the code

Swoole coroutine usage example and detailed explanation

$server = new Swoole\Http\ server ('127.0.0.1', 9501, SWOOLE_BASE); When the onRequest event callback is called, the C function coro_create is called to create a coroutine, and the CPU register state and ZendVM stack information are stored at this point in time. $server->on('Request', function($Request, $response) {$Mysql = new Swoole\Coroutine\ Mysql (); Coro_save = Zend VM context and coro_yield = Zend VM context and coro_yield = Zend VM context and coro_yield = Zend VM context The current request will hang. // When the coroutine cedes control, it continues to enter EventLoop to process other events, and Swoole continues to process requests from other clients. $res = $mysql - > connect ([' host '= >' 127.0.0.1 ', 'user' = > 'root' and 'password' = > 'root', 'database' = > 'test']); // After the IO event is complete, the MySQL connection succeeds or fails. The underlying C function coro_resume is called to restore the corresponding coroutine, restore the ZendVM context, and continue to execute PHP code. if ($res == false) { $response->end("MySQL connect fail"); return; $ret = $mysql->query('show tables', 2); $ret = $mysql->query('show tables', 2); // When all is done, call the end method to return the result and destroy the coroutine. $response->end('swoole response is ok, result='.var_export($ret, true)); }); $server->start();Copy the code

3. Go coroutine goroutine

1 Goroutine is a lightweight thread. Go supports native coroutines at the language level. 2 Go coroutines have very little overhead compared to threads. The stack overhead of the 3 Go coroutine is only 2KB, which can grow and shrink as the program needs, while the thread must specify the stack size, and the stack size is fixed. 4 Goroutine is implemented through the GPM scheduling model.

M: indicates the kernel level thread, an M is a thread, goroutine runs above M. G: Represents a Goroutine that has its own stack. P: Processor. It is mainly used to execute goroutine, and it also maintains a Goroutine queue.

Go encapsulates and processes Goroutine scheduling in multiple aspects such as Runtime and system call. In case of long execution or system call, Go will actively transfer the CPU of the current coroutine and let other coroutines schedule execution.

The native layer of the Go language supports a coroutine layer without declaring a coroutine environment.

Package main import "FMT" func main() {// Start a coroutine directly with the Go keyword. go func() { fmt.Println("Hello Go!" ()})}Copy the code

Go coroutines are multithreaded and can take advantage of multi-core cpus, where more than one coroutine may be executing at the same time.

Package main import (" FMT ""time") func main() {// If this parameter is set to 1, the output will be the same each time. Var I int64 for I = 0; var I int64 for I = 0; i < 4; Sleep(1 * time.second) FMT.Printf("hello %d \n", I)}(I)} FMT.Println("hello main") // Wait for the other coroutines to finish, if not, the other coroutines will exit after main. Sleep(10 * time.second)} $go run test. Go hello main hello 2 hello 1 hello 0 hello 3 Run test.go hello main hello 2 hello 0 hello 3 hello 1 // The output is different each timeCopy the code

Go coroutine use example and detailed explanation

package main import ( "fmt" "github.com/jinzhu/gorm" "net/http" "time" ) import _ "github.com/go-sql-driver/mysql" func The main () {DSN: = FMT. Sprintf (" % v: v % @ (% % v: v) / % v? Charset = utf8 & parseTime = = True&loc Local ", "root", "root", "127.0.0.1", "3306", "fastadmin", ) db, err := gorm.Open("mysql", dsn) if err ! = nil { fmt.Printf("mysql connection failure, error: (%v)", Err.error ()) return} db.db ().setMaxIdleconns (10) // Set the connection pool db.db ().setMaxOpenConns (100) // Set the maximum number of connections to the database db.DB().SetConnMaxLifetime(time.Second * 7) http.HandleFunc("/test", func(writer http.ResponseWriter, Request * HTTP request) {/ / HTTP request is processing in coroutines/SRC/net/HTTP / / in the source code for server Go: 2851 lines ` Go c.s. erve ` (CTX) Var name string row := db.table ("fa_auth_rule").Where("id =? , 1).Select("name").Row() err = row.Scan(&name) if err ! = nil { fmt.Printf("error: %v", err) return } fmt.Printf("name: % v \ n ", name)}) HTTP. ListenAndServe (" 0.0.0.0:8001, "nil)}Copy the code

Iv. Case analysis

Background: In our integration policy service system, mongodb storage is used, but Swoole does not provide the mongodb coroutine client.

In this scenario, synchronization blocking occurs when connecting and operating Mongodb, and coroutine switching cannot occur, resulting in the whole process blocking.

During this time, the process can no longer handle new requests, making the system less concurrency.

Use a synchronized mongodb client

$server->on('Request', function($Request, $response) {$server->on('Request', function($Request, $response) { $m = new MongoClient(); $db = $m->test; $collection = $db->runoob; / collection/choice / / update document $collection - > update (array (" title "= >" mongo "), array (' $set '= > array (" title "= >" Swoole "))); $cursor = $collection->find(); foreach ($cursor as $document) { echo $document["title"] . "\n"; }}}Copy the code

Use Server->taskCo to asynchronize mongodb operations

$server->on('Task', function (swoole_server $serv, $task_id, $worker_id, $data) { $m = new MongoClient(); $db = $m->test; $collection = $db->runoob; / collection/choice / / update document $collection - > update (array (" title "= >" mongo "), array (' $set '= > array (" title "= >" Swoole "))); $cursor = $collection->find(); foreach ($cursor as $document) { $data = $document["title"]; } return $data; }); $server->on('Request', function ($Request, $response) use ($server) { Post to an asynchronous task. // After Posting to an asynchronous task, a coroutine switch will occur and other requests can continue to be processed, providing concurrency capabilities. $tasks[] = "hello world"; $result = $server - > taskCo ($tasks, 0.5); $response->end('Test End, Result: '.var_export($result, true)); });Copy the code

These two ways of using Swoole are commonly used. So how do we deal with this synchronization in Go?

In fact, there is no need to worry about this problem in Go language. As we mentioned before, Go already supports coroutines at the language level. As long as IO operations occur, network requests will have coroutine switching.

This is why the Go language naturally supports high concurrency.

package main import ( "fmt" "gopkg.in/mgo.v2" "net/http" ) func main() { http.HandleFunc("/test", Func (writer http.ResponseWriter, request *http. request) {session, err := mgo.Dial("127.0.0.1:27017") if err! = nil { fmt.Printf("Error: %v \n", err) return } session.SetMode(mgo.Monotonic, C := session.db ("test").c ("runoob") FMT.Printf("Connect %v \n", c)}) HTTP.ListenAndServe("0.0.0.0:8001", nil)}Copy the code

Parallel: A CPU can execute only one task at a time. Multiple cpus are required to execute multiple tasks at the same time.

Concurrency: CPU switching time tasks are very fast, and it feels like many tasks are executing at the same time.

5. Scheduling of coroutine CPU intensive scenarios

We talked about scheduling based on IO intensive scenarios. So what about CPU intensive scenarios?

In Swoole V4.3.2, scheduling of coroutine CPU intensive scenarios is already supported.

To support CPU intensive scheduling, add the enable-scheduler-tick option at compile time to enable the tick scheduler.

Second, we need to manually declare(tick=N) syntax to implement coroutine scheduling.

<? php declare(ticks=1000); $max_msec = 10; Swoole\Coroutine::set([ 'max_exec_msec' => $max_msec, ]); $s = microtime(1); echo "start\n"; $flag = 1; go(function () use (&$flag, $max_msec){ echo "coro 1 start to loop for $max_msec msec\n"; $i = 0; while($flag) { $i ++; } echo "coro 1 can exit\n"; }); $t = microtime(1); $u = $t-$s; echo "shedule use time ".round($u * 1000, 5)." ms\n"; go(function () use (&$flag){ echo "coro 2 set flag = false\n"; $flag = false; }); echo "end\n"; Start coro 1 start to loop for 10 msec shedule use time 10.2849 ms coro 2 set flag = false end coro 1 can exitCopy the code

In CPU-intensive operations, Go may cause the coroutine to fail to preempt the CPU and remain suspended. Gosched() suspends the current coroutine and gives the CPU to another coroutine.

Package main import (" FMT ""time") func main() {// If set to single thread, the first coroutine cannot give time slice // the second coroutine cannot get time slice, blocking wait. // runtime.GOMAXPROCS(1) flag := true go func() { fmt.Printf("coroutine one start \n") i := 0 for flag { i++ // If you add this line of code, the coroutine can make the time slice // this since fmt.Printf is an inline function, this is a special case // fmt.Printf(" I: %d \n", i) } fmt.Printf("coroutine one exit \n") }() go func() { fmt.Printf("coroutine two start \n") flag = false FMT.Printf("coroutine two exit \n")}() time.sleep (5 * time.second) FMT.Printf("end \n")} coroutine one start coroutine two start coroutine two exit coroutine one exit endCopy the code

Note: time.sleep() simulates IO operations, and for i++ simulates CPU intensive operations.

conclusion

  • Coroutines are lightweight threads with very little overhead.
  • Swoole’s coroutine client needs to be used in the context of the coroutine.
  • Since Swoole V4.3.2, scheduling for coroutine CPU intensive scenarios has been supported.
  • The Go language level already fully supports coroutines.