进程就是应用程序的启动实例。php
例如:打开一个软件,就是开启了一个进程。mysql
进程拥有代码和打开的文件资源,数据资源,独立的内存空间。laravel
线程属于进程,是程序的执行者。git
一个进程至少包含一个主线程,也能够有更多的子线程。github
线程有两种调度策略,一是:分时调度,二是:抢占式调度。sql
协程是轻量级线程, 协程的建立、切换、挂起、销毁所有为内存操做,消耗是很是低的。mongodb
协程是属于线程,协程是在线程里执行的。shell
协程的调度是用户手动切换的,因此又叫用户空间线程。数据库
协程的调度策略是:协做式调度。编程
目前主流语言基本上都选择了多线程做为并发设施,与线程相关的概念就是抢占式多任务(Preemptive multitasking),而与协程相关的是协做式多任务。
其实不论是进程仍是线程,每次阻塞、切换都须要陷入系统调用(system call),先让CPU跑操做系统的调度程序,而后再由调度程序决定该跑哪个进程(线程)。
并且因为抢占式调度执行顺序没法肯定的特色,使用线程时须要很是当心地处理同步问题,而协程彻底不存在这个问题(事件驱动和异步程序也有一样的优势)。
由于协程是用户本身来编写调度逻辑的,对于咱们的CPU来讲,协程实际上是单线程,因此CPU不用去考虑怎么调度、切换上下文,这就省去了CPU的切换开销,因此协程在必定程度上又好于多线程。
多线程编程是比较困难的, 由于调度程序任什么时候候都能中断线程, 必须记住保留锁, 去保护程序中重要部分, 防止多线程在执行的过程当中断。
而协程默认会作好全方位保护, 以防止中断。咱们必须显示产出才能让程序的余下部分运行。对协程来讲, 无需保留锁, 而在多个线程之间同步操做, 协程自身就会同步, 由于在任意时刻, 只有一个协程运行。
总结下大概下面几点:
无需系统内核的上下文切换,减少开销;
无需原子操做锁定及同步的开销,不用担忧资源共享的问题;
单线程便可实现高并发,单核 CPU 即使支持上万的协程都不是问题,
因此很适合用于高并发处理,尤为是在应用在网络爬虫中。
Swoole 的协程客户端必须在协程的上下文环境中使用。
// 第一种状况:Request 回调自己是协程环境 $server->on('Request', function($request, $response) { // 建立 Mysql 协程客户端 $mysql = new Swoole\Coroutine\MySQL(); $mysql->connect([]); $mysql->query(); }); // 第二种状况:WorkerStart 回调不是协程环境 $server->on('WorkerStart', function() { // 须要先声明一个协程环境,才能使用协程客户端 go(function(){ // 建立 Mysql 协程客户端 $mysql = new Swoole\Coroutine\MySQL(); $mysql->connect([]); $mysql->query(); }); });
Swoole 的协程是基于单线程的, 没法利用多核CPU,同一时间只有一个在调度。
// 启动 4 个协程 $n = 4; for ($i = 0; $i < $n; $i++) { go(function () use ($i) { // 模拟 IO 等待 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 1 Swoole 协程使用示例及详解 // 建立一个 Http 服务 $server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE); // 调用 onRequest 事件回调函数时,底层会调用 C 函数 coro_create 建立一个协程, // 同时保存这个时间点的 CPU 寄存器状态和 ZendVM stack 信息。 $server->on('Request', function($request, $response) { // 建立一个 Mysql 的协程客户端 $mysql = new Swoole\Coroutine\MySQL(); // 调用 mysql->connect 时发生 IO 操做,底层会调用 C 函数 coro_save 保存当前协程的状态, // 包括 Zend VM 上下文以及协程描述的信息,并调用 coro_yield 让出程序控制权,当前的请求会挂起。 // 当协程让出控制权以后,会继续进入 EventLoop 处理其余事件,这时 Swoole 会继续去处理其余客户端发来的 Request。 $res = $mysql->connect([ 'host' => '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test' ]); // IO 事件完成后,MySQL 链接成功或失败,底层调用 C 函数 coro_resume 恢复对应的协程,恢复 ZendVM 上下文,继续向下执行 PHP 代码。 if ($res == false) { $response->end("MySQL connect fail"); return; } // mysql->query 的执行过程和 mysql->connect 一致,也会进行一次协程切换调度 $ret = $mysql->query('show tables', 2); // 全部操做完成后,调用 end 方法返回结果,并销毁此协程。 $response->end('swoole response is ok, result='.var_export($ret, true)); }); // 启动服务 $server->start();
goroutine 是轻量级的线程,Go 语言从语言层面就支持原生协程。
Go 协程与线程相比,开销很是小。
Go 协程的堆栈开销只用2KB,它能够根据程序的须要增大和缩小,而线程必须指定堆栈的大小,而且堆栈的大小都是固定的。
goroutine 是经过 GPM 调度模型实现的。
M: 表示内核级线程,一个 M 就是一个线程,goroutine 跑在 M 之上的。
G: 表示一个 goroutine,它有本身的栈。
P: 全称是 Processor,处理器。它主要用来执行 goroutine 的,同时它也维护了一个 goroutine 队列。
Go 在 runtime、系统调用等多个方面对 goroutine 调度进行了封装和处理,当遇到长时间执行或进行系统调用时,会主动把当前协程的 CPU 转让出去,让其余协程调度执行。
Go 语言原生层面就支持协层,不须要声明协程环境。
package main import "fmt" func main() { // 直接经过 Go 关键字,就能够启动一个协程。 go func() { fmt.Println("Hello Go!") }() }
Go 协程是基于多线程的,能够利用多核 CPU,同一时间可能会有多个协程在执行。
package main import ( "fmt" "time" ) func main() { // 设置这个参数,能够模拟单线程与 Swoole 的协程作比较 // 若是这个参数设置成 1,则每次输出的结果都同样。 // runtime.GOMAXPROCS(1) // 启动 4 个协程 var i int64 for i = 0; i < 4; i++ { go func(i int64) { // 模拟 IO 等待 time.Sleep(1 * time.Second) fmt.Printf("hello %d \n", i) }(i) } fmt.Println("hello main") // 等待其余的协程执行完,若是不等待的话, // main 执行完退出后,其余的协程也会相继退出。 time.Sleep(10 * time.Second) } // 第一次输出的结果 $ go run test.go hello main hello 2 hello 1 hello 0 hello 3 // 第二次输出的结果 $ go run test.go hello main hello 2 hello 0 hello 3 hello 1 // 依次类推,每次输出的结果都不同
Go 协程使用示例及详解
package main import ( "fmt" "github.com/jinzhu/gorm" "net/http" "time" ) import _ "github.com/go-sql-driver/mysql" func 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) // 设置链接池 db.DB().SetMaxOpenConns(100) // 设置与数据库创建链接的最大数目 db.DB().SetConnMaxLifetime(time.Second * 7) http.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) { // http Request 是在协程中处理的 // 在 Go 源码 src/net/http/server.go:2851 行处 `go c.serve(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) }
背景:
在咱们的积分策略服务系统中,使用到了 mongodb 存储,可是 swoole 没有提供 mongodb 协程客户端。
那么这种场景下,在链接及操做 Mongodb 时会发生同步阻塞,没法发生协程切换,致使整个进程都会阻塞。
在这段时间内,进程将没法再处理新的请求,这使得系统的并发性大大下降。
使用同步的 mongodb 客户端
$server->on('Request', function($request, $response) { // swoole 没有提供协程客户端,那么只能使用同步客户端 // 这种状况下,进程阻塞,没法切换协程 $m = new MongoClient(); // 链接到mongodb $db = $m->test; // 选择一个数据库 $collection = $db->runoob; // 选择集合 // 更新文档 $collection->update(array("title"=>"MongoDB"), array('$set'=>array("title"=>"Swoole"))); $cursor = $collection->find(); foreach ($cursor as $document) { echo $document["title"] . "\n"; } }}
经过使用 Server->taskCo 来异步化对 mongodb 的操做
$server->on('Task', function (swoole_server $serv, $task_id, $worker_id, $data) { $m = new MongoClient(); // 链接到mongodb $db = $m->test; // 选择一个数据库 $collection = $db->runoob; // 选择集合 // 更新文档 $collection->update(array("title"=>"MongoDB"), 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) { // 经过 $server->taskCo() 把对 mongodb 的操做,投递到异步 task 中。 // 投递到异步 task 后,将发生协程切换,能够继续处理其余的请求,提供并发能力。 $tasks[] = "hello world"; $result = $server->taskCo($tasks, 0.5); $response->end('Test End, Result: '.var_export($result, true)); });
上面两种使用方式就是 Swoole 中经常使用的方法了。
那么咱们在 Go 中怎么处理这种同步的问题呢 ?
实际上在 Go 语言中就不用担忧这个问题了,如咱们以前所说到的,
Go 在语言层面就已经支持协程了,只要是发生 IO 操做,网络请求都会发生协程切换。
这也就是 Go 语言天生以来就支持高并发的缘由了。
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, true) c := session.DB("test").C("runoob") fmt.Printf("Connect %v \n", c) }) http.ListenAndServe("0.0.0.0:8001", nil) }
并行:同一时刻,同一个 CPU 只能执行同一个任务,要同时执行多个任务,就须要有多个 CPU。
并发:CPU 切换时间任务很是快,就会感受到有不少任务在同时执行。
咱们上面说到都是基于 IO 密集场景的调度。
那么若是是 CPU 密集型的场景,应该怎么处理呢?
在 Swoole v4.3.2 版本中,已经支持了协程 CPU 密集场景的调度。
想要支持 CPU 密集调度,须要在编译时增长编译选项 --enable-scheduler-tick 开启 tick 调度器。
其次还须要咱们手动声明 declare(tick=N) 语法功能来实现协程调度。
<?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 exit
Go 在 CPU 密集运算时,有可能致使协程没法抢占 CPU 会一直挂起。这时候就须要显示的调用代码 runtime.Gosched() 挂起当前协程,让出 CPU 给其余的协程。
package main import ( "fmt" "time" ) func main() { // 若是设置单线程,则第一个协程没法让出时间片 // 第二个协程一直得不到时间片,阻塞等待。 // runtime.GOMAXPROCS(1) flag := true go func() { fmt.Printf("coroutine one start \n") i := 0 for flag { i++ // 若是加了这行代码,协程可让时间片 // 这个由于 fmt.Printf 是内联函数,这是种特殊状况 // 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 end
注:time.sleep() 模拟 IO 操做,for i++ 模拟 CPU 密集运算。
协程是轻量级的线程,开销很小。
Swoole 的协程客户端须要在协程的上下文环境中使用。
在 Swoole v4.3.2 版本以后,已经支持协程 CPU 密集场景调度。
Go 语言层面就已经彻底支持协程了。
好了各位,以上就是这篇文章的所有内容了,能看到这里的人呀,都是人才。以前说过,PHP方面的技术点不少,也是由于太多了,实在是写不过来,写过来了你们也不会看的太多,因此我这里把它整理成了PDF和文档,若是有须要的能够
更多学习内容能够访问【对标大厂】精品PHP架构师教程目录大全,只要你能看完保证薪资上升一个台阶(持续更新)
以上内容但愿帮助到你们,不少PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提高,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货须要的能够免费分享给你们,须要的能够加入个人 PHP技术交流群