协程swoole对比golang

- 协程概念
协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。
协程具有以下几个特点
- 用户态执行,完全由程序所控制,不是被操作系统内核所管理的
- 适用于处理IO密集型任务,至于什么是IO密集型任务这里就不做详细介绍了,主要区别于CPU密集型任务
- 将线程中的竞争资源,转化成协作运行
- 通道(Channel)的方式进行协程间的通信
- 少量的上下文切换开销,主要是运行在线程上,对比进程的上下文切换是保存在栈资源当中,而协程是异步非阻塞的,相当于用户态线程中的队列任务,只需要利用channel作为回调即可,不需要在任务完成后二次的资源抢夺
Swoole 协程
单进程模型,实现方式相对简单 / 不用加锁 / 性能高,基于单线程的,无法利用CPU多核,运行在用户进程中
echo "main start
";
go(function () {
echo "coro ".co::getcid()." start";
co::sleep(.1);echo "coro ".co::getcid()." end";
});
echo "main flag
";
go(function () {
echo "coro ".co::getcid()." start";
co::sleep(.1);echo "coro ".co::getcid()." end";
});
echo "end
";
Golang 协程
MPG模型,运行在操作系统内核CPU上,因此可以利用CPU多核
M: 表示内核级线程,一个 M 就是一个线程,goroutine 跑在 M 之上的。
G: 表示一个 goroutine,它有自己的栈。
P: 全称是 Processor,处理器。它主要用来执行 goroutine 的,同时它也维护了一个 goroutine 队列。
Go 语言原生层面就支持协层,不需要声明协程环境。
这里还有一句著名的话
要通过共享内存来通信,相反,应该通过通信来共享内存
package main
import (
"fmt""sync""math/rand""container/ring""strings""time")
var (
wg sync.WaitGroup // 用于goroutine计数times = 2 // 每个选手发球次数nums = 4 // 多少个选手serveTotals = nums * times // 总发球次数score_balls_A = make([]TableTennis, 0, serveTotals) // A的得分球score_balls_B = make([]TableTennis, 0, serveTotals) // B的得分球turn = ring.New(4) // 发球顺序serveMetux sync.Mutex // 发球锁catch_chanel_B = make(chan TableTennis, 0) // B队伍接球的通道catch_chanel_A = make(chan TableTennis, 0) // A队伍接球的通道balls_ids = make(chan int, serveTotals) // 球的id)
// 乒乓球
type TableTennis struct {
id inttrail string // 球的轨迹}
func serve() {
defer wg.Done()// 初始化发球顺序turn.Value = "A1"turn = turn.Next()turn.Value = "B1"turn = turn.Next()turn.Value = "A2"turn = turn.Next()turn.Value = "B2"// 开始发球for i := 0; i < times; i++ {for j := 0; j < nums; j++ {serveMetux.Lock() // 解锁时发下一个球turn = turn.Next()name := turn.Value.(string)t := TableTennis{<-balls_ids, name + "-in"}if name[0] == "A" {catch_chanel_B <- t} else {catch_chanel_A <- t}}}time.Sleep(time.Second) // 等待player goroutine对catch_chanel的使用close(catch_chanel_A)close(catch_chanel_B)}
// A队选手
func playerA(name string, rate int) {
defer wg.Done() // 延迟递减计数for t := range catch_chanel_A {// 2. 将球击打出去rest := shot(rate)// 3. 记录球的轨迹t.trail += "-" + name + "-" + rest// 球出界if strings.Compare("out", rest) == 0 {// 对方得分score_balls_B = append(score_balls_B, t)fmt.Println(t)serveMetux.Unlock()continue}// 4. 对面队伍准备接球catch_chanel_B <- t}}
// B队选手
func playerB(name string, rate int) {
defer wg.Done() // 延迟递减计数for t := range catch_chanel_B {// 2. 将球击打出去rest := shot(rate)// 3. 记录球的轨迹t.trail += "-" + name + "-" + rest// 球出界if strings.Compare("out", rest) == 0 {// 对方得分score_balls_A = append(score_balls_A, t)fmt.Println(t)serveMetux.Unlock()continue}// 4. 对面队伍准备接球catch_chanel_A <- t}}
// 击球
func shot(rate int) string {
if rand.Intn(100) < rate {return "in"} else {return "out"}}
func main() {
fmt.Println("比赛开始...")// 初始化球的idfor i := 0; i < serveTotals; i++ {balls_ids <- i + 1}// 初始化发球顺序wg.Add(nums + 1) // 累加计数go serve()//time.Sleep(time.Second)go playerA("A1", 45)go playerA("A2", 60)go playerB("B1", 50)go playerB("B2", 90)wg.Wait()fmt.Println("比赛结束.")fmt.Printf("A : B = (%d, %d)", len(score_balls_A), len(score_balls_B))
for _, t := range score_balls_A {fmt.Println(t)}fmt.Println()for _, t := range score_balls_B {fmt.Println(t)}}
另外补充:管道channel的实现
我们知道管道是操作系统中通信的一种方式,它的特点是半双工通信,同一时间只有单方向的数据传输
swoole和golang都实现了channel, 但是并不是基于操作系统的管道实现的,只用应用了相同的原理
本质上是运行在内存中的队列,数据结构 Channel,底层基于共享内存 + Mutex 互斥锁实现,实现用户态的高性能内存队列。
这篇文章有详细的swoole源码分析https://zhuanlan.zhihu.com/p/45020194
并且channel分为了无缓冲通道和有缓冲通道,通道中无数据或者缓冲满了都会形成阻塞。
以上是 协程swoole对比golang 的全部内容, 来源链接: utcz.com/z/517156.html

