go语言 - 协程

本文详细介绍了Go语言中的goroutine,这是一种轻量级的并发机制,支持成千上万的并发任务。goroutine与线程相比更加高效,易于管理。文章讨论了goroutine的定义、创建,以及如何利用`runtime`包的`Gosched()`、`Goexit()`和`GOMAXPROCS()`进行任务调度。此外,还提到了多任务竞争问题和通过channel实现的同步通信机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

go在语言层面对并发编程提供支持,通过goroutine(协程)机制;goroutine 是一种非常轻量级的实现,可在单个进程里执行成千上万的并发任务,只需要在函数调用语句前添加go 关键字,即可创建并发执行单元;

有人把Go 比作21世纪的C语言,第一是因为Go语言设计简单,第二是21世纪最重要的就是并发程序设计,而Go从语言层面就支持了并发,同时,并发程序的内存管理有时候是非常复杂的,而Go语言提供了自动垃圾回收机制

Go 语言为并发编程而内置的上层API基于CSP(COMMUNICATING SEQUENTIAL PROCESSES, 顺序通信进程)模型,这就意味着显示锁都是可以编码的。因为Go 语言通过安全的通道发送和接收数据以实现同步,大大地简化了并发程序的编写 

一般情况下,一个普通的桌面计算机跑10----20几个线程就有点负载过大了,但是同样一台机器却可以轻松地让成百上千甚至过万个goroutine 进行资源竞争

 

gouroutine

定义

goroutine 是Go语言并发设计的核心。goroutine 说到底其实就是协程,它比线程更小,十几个goroutine 可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine 之间的内存共享,执行goroutine 只需极少的栈内存(4---5KB),当然会根据相应的数据伸缩;也正因为如此,可同时运行成千上万个并发任务。goroutine比thread 更易用、更高效、更轻便。

 

创建goroutine


import (
	"fmt"
	"runtime"
	"time"
)

func main() {

	//创建 goroutine, 两个死循环同时执行
	go newTask()

	for {
		fmt.Println("this is a main goroutine")
		time.Sleep(time.Second)
	}
}


func newTask() {
	for {
		fmt.Println("this is a newTask goroutine")
		time.Sleep(time.Second)
	}
}

执行结果如下:

~/work_space/go_works/src/admin/concurrent » go run concurrent.go                                                                                ryanxu@iterm
this is a main goroutine
this is a newTask goroutine
this is a newTask goroutine
this is a main goroutine
this is a main goroutine
this is a newTask goroutine
this is a main goroutine
this is a newTask goroutine
this is a newTask goroutine
this is a main goroutine
this is a main goroutine
this is a newTask goroutine
^Csignal: interrupt

 

主协程退出了, 其他子协程也会跟着退出


import (
	"fmt"
	"time"
)

func main() {
	
	go func() {
		i := 0
		for {
			i++
			fmt.Println("子协程 i = ", i)
			time.Sleep(time.Second)
		}
	}()

	i := 0
	for {
		i++
		fmt.Println("主协程 i = ", i)
		time.Sleep(time.Second)

		if i == 2 {
			break
		}
	}
}

执行结果如下:

------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent » go run concurrent.go                                                                                ryanxu@iterm
主协程 i =  1
子协程 i =  1
子协程 i =  2
主协程 i =  2
------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent »    

当主协程(main 函数)中 i == 2 , break 跳出循环, 主函数执行完时, 子协程也跟着退出,无法继续执行

 

runtime 包

runtime.Gosched()

runtime.Gosched() 用于让出CPU时间片,让出当前goroutine 的执行权限,调度器安排其他等待的任务运行,并在下一次某个时候从该位置恢复执行

类似接力赛,A跑了一会儿碰到代码runtime.Gosched() 就把接力棒交给B, 然后A 歇着了B继续跑

import (
	"fmt"
	"runtime"
	"time"
)

func main() {

	go func() {
		for i := 0; i < 5; i++ {
			fmt.Println("hello go")
		}
	}()

	for i := 0; i < 2; i++ {
		//runtime.Gosched() 让出时间片,先让别的协程执行,待其执行完,再回来执行此协程
		runtime.Gosched()
		fmt.Println("hello")
	}
}

执行结果如下:

------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent » go run concurrent.go                                                                                ryanxu@iterm
hello go
hello go
hello go
hello go
hello go
hello
hello

runtime.Goexit()

import (
	"fmt"
	"runtime"
	"time"
)

func main() {

	go func() {
		fmt.Println("aaaaaaaaaaaaaaa")

		//调用别的函数
		test()

		fmt.Println("bbbbbbbbbbbbbbb")

	}()

	//特地写一个死循环,目的不让主协程结束
	for {

	}
}



//区分一下 return 和 runtime.Goexit() 的区别
func test() {
	defer fmt.Println("ccccccccccccccc")
	//return 终止此函数
	runtime.Goexit() //终止所在的协程
	fmt.Println("ddddddddddddddd")
}

执行结果如下:

~/work_space/go_works/src/admin/concurrent » go run concurrent.go                                                                                ryanxu@iterm
aaaaaaaaaaaaaaa
ccccccccccccccc
^Csignal: interrupt

runtime.GOMAXPROCS()


import (
	"fmt"
	"runtime"
	"time"
)

func main() {

	// n := runtime.GOMAXPROCS(1)
	n := runtime.GOMAXPROCS(4)
	fmt.Println("n = ", n)
	for {
		go fmt.Print(1)
		fmt.Print(0)
	}
}

修改注释代码,设置1 和设置为4 时, 根据不同的输出结果很容易看得出结果

多任务竞争问题


import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	go person1()
	go person2()
	//特意不让主函数结束
	for {

	}
}


//定义一个打印机,参数为字符串,按每个字符打印; 打印机属于公共资源
func Printer(str string) {
	for _, data := range str {
		fmt.Printf("%c", data)
		time.Sleep(time.Second)
	}
	fmt.Println()
}


//定义一个channel
var ch = make(chan int)

func person1() {

	Printer("hello")
	ch <- 111 //给管道写数据, 发送
}

func person2() {
	<-ch // 从管道获取数据, 接收,如果通道没有数据,则会阻塞
	Printer("world")
}

输出结果如下:

------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent » go run concurrent.go                                                                                ryanxu@iterm
hello
world
^Csignal: interrupt
------------------------------------------------------------
~/work_space/go_works/src/admin/concurrent »      

这个地方用了channel 来进行通信,在person1 中未向管道ch 中发送数据时, person2 中不能接受到数据,此时person2阻塞,继而保证先完整的输出hello ,再去输出world ;当然,测试过程中,可以将 channel 对象 ch 的所在的三行代码注释掉,然后再看执行效果, 因为多任务编程,确实比较抽象。

举个现实例子:

Tom 和 John 两个人都要使用打印机打印文件, 正常的逻辑是, Tom 打印完了, 然后告诉John,John 你可以来打印了; 而这个告诉的机制就是程序中的channel (通道);

BTW: 下一篇会详细介绍channel 
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值