协程swoole对比golang

编程

  • 协程概念

    协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

    协程具有以下几个特点

  1. 用户态执行,完全由程序所控制,不是被操作系统内核所管理的
  2. 适用于处理IO密集型任务,至于什么是IO密集型任务这里就不做详细介绍了,主要区别于CPU密集型任务
  3. 将线程中的竞争资源,转化成协作运行
  4. 通道(Channel)的方式进行协程间的通信
  5. 少量的上下文切换开销,主要是运行在线程上,对比进程的上下文切换是保存在栈资源当中,而协程是异步非阻塞的,相当于用户态线程中的队列任务,只需要利用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    int

  • trail 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("比赛开始...")

  • // 初始化球的id

  • for 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

回到顶部