go语言协程
【摘要】 概念在日常程序执行总,经常会有三个概念:进程、线程、协程,其定义如下:进程:一个独立程序执行的过程,进程是动态产生的,程序执行时候产生,程序结束则消亡,在操作系统内可以同时执行多个进程,每个进程都是系统进行资源分配和调度的一个独立单位,进程由内核进行调度线程:程序执行的一个单一的控制流,是程序执行的最小单元,一个进程可以由多个线程组成,线程由内核进行调度协程:一种更轻量级的程序并发执行方法...
概念
在日常程序执行总,经常会有三个概念:进程
、线程
、协程
,其定义如下:
- 进程:一个独立程序执行的过程,进程是动态产生的,程序执行时候产生,程序结束则消亡,在操作系统内可以同时执行多个进程,每个进程都是系统进行资源分配和调度的一个独立单位,进程由内核进行调度
- 线程:程序执行的一个单一的控制流,是程序执行的最小单元,一个进程可以由多个线程组成,线程由内核进行调度
- 协程:一种更轻量级的程序并发执行方法,协程对内核透明,内核并不知道协程的存在,协程在资源消耗和切换开销方便都有着优秀的表现
对比项目 | 进程 | 协程 |
---|---|---|
内存消耗 | 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的输出作为线程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)