GO协程理解和应用场景

1、概述        

最近在倒腾GO语言,用来做了一段时间研发后,发现一些特点,在此记录一下。

        首先学习了下他的语言语法,发现规则和其他语言规则有点类似,函数是通过大括号来进行规范,条件语句也是通过大括号在规范,然后就是else语句必须放在if的结束大括号后面,否则会报错;语法简单,不需要像C/C++语言那样需要分号来结束每条语句,直接换行即可,也不需要像python语言那样需要强要求的换行来标识语句和函数;最后就是协程,协程可以算是go语言的最大的特点,也是go语言诞生的初衷。

        很多文章有这么一句话叫做:一核有难多核围观,意思是针对多核CPU一个核忙得要死要活,剩下的核确实闲置的。出现上面的原因就是写的程序是单核处理器,不是针对多核CPU的高并发程序。在这里也记录下并发和并行的区别,借用看到资料的解释,比较形象,并发就是使用同一个锅炒不同的菜,菜品在锅中随时切换;并行就是有多个锅,每个锅同时炒不同的菜。写完这个比喻我突然想到了另一更加形象的比喻:并发就是你拿着一把刀切菜,一会切白菜,一会切萝卜,一会切茄子,你的这把刀就是CPU一个核,然后并发的去切很多菜;并行就是你用多把刀同时切不同的菜,哈哈,感觉更形象了,是不是?

        (补充一下,可能有点乱:并发不是并行,并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了在很多情况下,并发的效果比并行好,因为操作系统和硬件的总资源一般很少,但能支持系统同时做很多事情。)

2、协程实现和协程交互       

协程我们在代码里面实现直接在函数调用前面直接加go就可以实现协程调用,如下所示:

func testfun(){

    //do something
}

go testfun()

这种用法还是相当方便的,只需要加一个关键字就可以实现协程功能。但是这种一般都是独立的协程,如果需要协程之间相互通信,go语言也提供多种方法,第一种就是跟C/C++一样的加锁,方法如下:

lock.Lock()
testname = "newname"    //testname为全局变量,多线程操作的时候需要锁住变量
//解锁
lock.Unlock()

但是go语言虽然支持这种锁的方式进行线程之间的通信,但是go一般不用这种方式,go一般都用通道(channel)的形式来进行协程之间数据交互。下面将简述使用通道channel来实现协程之间的交互。

3、创建、使用 channel

channel 是一个通道、队列,那么我们关心的应该就是如何创建这个通道、将数据装到通道中、从通道中提取数据。 golang 为它设计了一个操作符:
left <- right,当 left 为 channel 时,表示向通道中写入数据(发送),并且如果通道存在数据,写入会被阻塞,所以可以建立一个大小为n的通道,当写入数量大于n时,通道才会堵塞;当 right 为通道时,表示从通道提取数据(接收)。

package main

import  "fmt"

func main() {
    simpleChan()
}

func simpleChan() {
    // 声明一 chan 类型的字符串的变量
    var ch chan string
    ch = make(chan string)
    // 向 channel 中发送 string 类型数据
    go func() {    //前面括号里传形式参数
        ch <- "ping"
    }()    //前面括号里传实参参数
    // 创建一个 string 类型的变量,用来保存从 channel 队列提取的数据
    var v string
    v = <-ch
    fmt.Println(v)
}

        上面的例子中创建了一个通道ch,然后并make了一块内存,这个通道实现了一个类似队列的功能,有数据写入,里面如果没有被读走,就排队等候。上面代码里面的两个操作语句就可以完成了数据入队列(ch <- "ping"),数据出队列(v = <-ch)的动作。这里有个问题需要注意,channel 的接收与发送需要分别在两个 goroutine 中,如果你是直接看英文的文档、或者其他介绍的文章,可能没有指出这个要求。它是跨协程的。如果ch <- "ping"不用协程调用,跑起来会报错。

        从上面的例子可以看到协程通过通道ch来实现数据传递,这个通道ch就类似枷锁操作的变量,通道也是一种数据结构,跟使用枷锁方式操作变量一样,通道定义的时候也需要定下来通道的类型,定义好后不能修改。

4、数据协程、通道channel使用实例

这个例子是我在搜索资料看到的,感觉还行,放在记录一下,例子中主要展示的例子原意是有2个干活的worker,然后有五个工作job,需要这两个人来完成这五个工作,功能实现里面还定义了五个工作完成的结果。如下所示:

// channel.go
package main

import (
    "fmt"
    "time"
)

func main() {
    workpools()
}

func workpools() {
    const number_of_jobs = 5
    const number_of_workers = 2
    jobs := make(chan int, number_of_jobs)
    results := make(chan string, number_of_jobs)

    // 向 任务队列写入任务
    for i := 1; i <= number_of_jobs; i++ {
        jobs <- i
    }
    fmt.Println("布置 job 后,关闭 jobs channel")
    close(jobs)

	 // 控制并行度,每个 worker 函数都运行在单独的 goroutine 中
	for w := 1; w <= number_of_workers; w++ {
        go worker(w, jobs, results)
    }

    // 监听 results channel,只要有内容就会被取走
    for i := 1; i <= number_of_jobs; i++ {
        fmt.Printf("结果: %s\n", <-results)
    }
}

// worker 逻辑:一个不断从 jobs chan 中取任务的循环
// 并将结果放在 out channel 中待取
func worker(id int, jobs <-chan int, out chan<- string) {
    fmt.Printf("worker #%d 启动\n", id)
    for job := range jobs {
        fmt.Printf("worker #%d 开始 工作%d\n", id, job)
        // sleep 模拟 『正在处理任务』
        time.Sleep(time.Millisecond * 500)
        fmt.Printf("worker #%d 结束 工作%d\n", id, job)

        out <- fmt.Sprintf("worker #%d 工作%d", id, job)
    }
    fmt.Printf("worker #%d 退出\n", id)
}

从例子中可以看到,逻辑是首先jobs是缓冲区为5的通道,所以先给工作通道布置了五个工作分别是工作1,2,3,4,5,然后就使用两个worker工作者1,2协程来完成这五个工作。运行代码可以看到如下打印信息:

布置 job 后,关闭 jobs channel
worker #2 启动
worker #2 开始 工作1
worker #1 启动
worker #1 开始 工作2
worker #1 结束 工作2
worker #2 结束 工作1
worker #2 开始 工作4
结果: worker #1 工作2
结果: worker #2 工作1
worker #1 开始 工作3
worker #2 结束 工作4
worker #2 开始 工作5
worker #1 结束 工作3
worker #1 退出
结果: worker #2 工作4
结果: worker #1 工作3
worker #2 结束 工作5
worker #2 退出
结果: worker #2 工作5

从上面的打印信息可以看到,两个工作者worker1,2,同时完成布置的jobs,里面的这句语句有点魔性,

for job := range jobs

在协程里面是都会使用这句语句的,根据资料说的是这个range是不管jobs里面的个数,只是从jobs里面去取出一个数据,如果里面没有了,循环就会结束。今天先写到这,后面再补充。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值