go语言协程

举报
Nick Qiu 发表于 2021/07/20 15:47:36 2021/07/20
【摘要】 概念在日常程序执行总,经常会有三个概念:进程、线程、协程,其定义如下:进程:一个独立程序执行的过程,进程是动态产生的,程序执行时候产生,程序结束则消亡,在操作系统内可以同时执行多个进程,每个进程都是系统进行资源分配和调度的一个独立单位,进程由内核进行调度线程:程序执行的一个单一的控制流,是程序执行的最小单元,一个进程可以由多个线程组成,线程由内核进行调度协程:一种更轻量级的程序并发执行方法...

概念

在日常程序执行总,经常会有三个概念:进程线程协程,其定义如下:

  • 进程:一个独立程序执行的过程,进程是动态产生的,程序执行时候产生,程序结束则消亡,在操作系统内可以同时执行多个进程,每个进程都是系统进行资源分配和调度的一个独立单位,进程由内核进行调度
  • 线程:程序执行的一个单一的控制流,是程序执行的最小单元,一个进程可以由多个线程组成,线程由内核进行调度
  • 协程:一种更轻量级的程序并发执行方法,协程对内核透明,内核并不知道协程的存在,协程在资源消耗和切换开销方便都有着优秀的表现
对比项目 进程 协程
内存消耗 8M级别 2KB级别
切换调度开销 大 - 16个寄存器 小 - 3个寄存器

如上我们知道协程在并发上有着比线程更优秀的表现,但其实协程的底层实现原理还是基于线程。

协程的简单应用

如下代码,启动协程只需要在调用函数前加关键字go即可,如下需要注意,如果主线程退出了之后协程也会跟着退出,所以在末尾添加了一个休眠,等待协程执行完毕。

package main

import (
	"fmt"
	"time"
)

func fun(s string)  {
	fmt.Println(s)
}
func main() {
	fmt.Println("This is a new test")
	go fun("hello there!")
	//休眠一段时间,避免携程还没运行主线程就退出了
	time.Sleep(100)
}

协程之间的通讯

go语言协程之间的同学,通常使用 Mutex、channel、select来实现,本章具体举例说明

互斥锁Mutex

  • 如下代码,协程在访问全局变量a时,因为没有添加锁定机制,执行顺序随机,会导致打印出来的数值不按顺序执行
package main

import (
	"fmt"
	"time"
)

var a int = 0

func fun() {
	a++
	fmt.Printf("%d\n",a)
}

func main() {
	//创建协程
	for i:=0;i<10;i++ {
		go fun()
	}
	time.Sleep(time.Microsecond * 2)
}

  • 输出结果如下,且反复运行执行的结果不同
3
2
1
5
6
7
8
9
4
10
  • 添加互斥锁优化后代码
package main

import (
	"fmt"
	"sync"
	"time"
)

var Mutex sync.Mutex
var a int = 0

func fun() {
	Mutex.Lock()   //加锁
	a++
	fmt.Printf("%d\n",a)
	Mutex.Unlock() //解锁
}

func main() {
	for i:=0;i<10;i++ {
		go fun() //创建协程
	}
	time.Sleep(time.Microsecond * 2)
}

该代码执行,可以看到输出打印文字则按1~10顺序输出

1
2
3
4
5
6
7
8
9
10

WaitGroup

任务组可以对一组任务进行计数,并在每次调用Done之后记录一个数量,当所有任务都完成了则退出

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	wg :=sync.WaitGroup{}
	wg.Add(10)  //初始化为10
	for i := 0; i < 10; i++ {
		go Go(&wg, i)
	}
    //等待任务组内的所有任务执行完毕
	wg.Wait()
}
func Go(wg *sync.WaitGroup, index int) {
	time.Sleep(time.Second*time.Duration(index))
	fmt.Println(index)
	wg.Done()   //执行一次减少1
}

通道channel

channel有三种操作: send(向channel投放数据)、receive(从channel接收数据)、close(关闭channel)

  • 生产者消费者
package main

import (
	"fmt"
	"time"
)

//生产者
func Produce(header interface{},pipe chan interface{} ,delay int){
	for{
		pipe <- header
		time.Sleep(time.Millisecond * time.Duration(delay))
	}
}

func Consume(pipe chan interface{}){
	for{
		message := <- pipe
		fmt.Println("消费:",message)
	}
}

func main() {
	channel := make(chan interface{})
	go Produce("汉堡包",channel,1000)
	go Produce("可乐",channel,500)
	Consume(channel)
}

  • 级联线程
Counter
SquarerPrinter

线程Counter的输出作为线程Squarer的输入,线程Squarer的输出作为线程Printer的输入,具体代码如下:

package main

import (
	"fmt"
)

func main() {
	naturals := make(chan int)
	squares := make(chan int)

	// Counter
	go func() {
		for x := 0; x < 100; x++ {
			naturals <- x
		}
		close(naturals)
	}()

	// Squarer
	go func() {
		for x := range naturals {
			squares <- x * x
		}
		close(squares)
	}()

	// Printer (in main goroutine)
	for x := range squares {
		fmt.Println(x)
	}
}

多个channel的监听Select

select字段可以监听多个channel的数据,其中每次循环的时候随机读相关的channel,如下代码程序最后会监听channel1和channel2,channel2返回之后由于主线程返回所以整个程序退出

package main

import (
	"fmt"
	"time"
)
func sender1(channel chan int) {
	time.Sleep(3 * time.Second)
	channel <- 1
	close(channel)
}
func sender2(channel chan int) {
	for  {
		time.Sleep(1 * time.Second)
		channel <- 1
	}
	close(channel)
}

func main() {
	channel1 := make(chan int)
	channel2 := make(chan int)
	go sender1(channel1)
	go sender2(channel2)
	for {
		select {
		case v, ok := <-channel1:
			if ok {
				fmt.Println("channel 1 data:",v)
			} else {
				fmt.Println("close 1")
				return
			}
		case v, ok := <-channel2:
			if ok{
				fmt.Println("channel 2 data:",v)
			} else {
				fmt.Println("close 2")
				return
			}
		default:
			//fmt.Println("waiting")
		}
	}
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。