Go语言入门12(协程 goroutine)

发布时间 2023-04-26 09:27:55作者: te9uila

协程

进程和线程

进程

​ 当运行一个应用程序的时候,操作系统会为这个应用程序启动一个进程。可以将这个进程看作一个包含了应用程序在运行中需要用到和维护的各种资源的容器。这些资源包括但不限于内存地址空间、文件和设备的句柄以及线程

线程

​ 一个线程是一个执行空间,这个空间会被操作系统调度来运行函数中所写的代码。每个进程至少包含一个线程,每个进程的初始线程被称作主线程。因为执行这个线程的空间是应用程序的本身的空间,所以当主线程终止时,应用程序也会终止。操作系统将线程调度到某个处理器上运行,这个处理器并不一定是进程所在的处理器

并发和并行

并发

​ 并发是指在一个逻辑处理器同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了,golang的并发通过切换多个线程打到减少物理处理器空闲等待的目的

并行

​ 并行是让不同的代码片段同时在不同的物理处理器上执行,大多数情况下,并发的效果比并行好,因为操作系统的硬件的总资源一般很少,但能支持系统同时做很多事情

goroutine协程

在一个协程中运行函数

​ 在一个协程中运行一个函数其实很简单,只要在函数调用前加上go即可

// 随便写一个demo函数
func Demo01(num int, contain string) {
	for i := 0; i < num; i++ {
		fmt.Println("这是一个goroutine", contain)
	}
}
// 在协程中运行此函数
func main() {
    go Demo01(10,"调用Demo函数")
	fmt.Println("over!")
}
//输出:
//   over!

​ 上述代码由于demo调用在协程中进行,所以并不影响主函数的运行,当主函数运行结束之后,代码运行直接停止,所以协程并没有输出任何内容,因此我们可以使用sleep方法让主函数进入等待状态

// 随便写一个demo函数
func Demo01(num int, contain string) {
	for i := 0; i < num; i++ {
		fmt.Println("这是一个goroutine", contain)
	}
}
// 在协程中运行此函数
func main() {
    go Demo01(10,"调用Demo函数")
    time.Sleep(1000000000)
	fmt.Println("over!")
}
//输出:
//   这是一个goroutine demo调用
//   这是一个goroutine demo调用
//   这是一个goroutine demo调用
//   这是一个goroutine demo调用
//   这是一个goroutine demo调用
//   over!

​ 但是这样子让主函数等待协程运行,听起来是个蛮蠢的想法,我们可以使用一个另一种方式解决问题,Waitgroup方法

waitgroup方法

​ 我们可以在使用waitgroup方法先实例化一个计数器,wg,在协程中使用wg.done进行操作计数器,从而达到主函数等待协程运行的一个目的

// demo方法
// 同时因为我们要对wg进行操作,因此要把wg的指针传递给函数
func Demo01(num int, contain string, wg *sync.WaitGroup) {
    // 因为wg,done一般来说都在函数最后使用,因此我们把他放在defer延迟处理函数里面
	defer wg.Done()
	for i := 0; i < num; i++ {
		fmt.Println("这是一个goroutine", contain)
		time.Sleep(1000000000)
	}
}

// main方法
func main() {
    // 实例化计数器wg
	var wg sync.WaitGroup
    // 要运行两个goroutine函数
	wg.Add(2)
	go functions.Demo01(2, "the first goroutine!", &wg)
	go functions.Demo01(3, "the second goroutine!", &wg)
    // 等待两个goroutine运行完毕
	wg.Wait()
    // 执行完毕后再输出over!
	fmt.Println("over!")
}
// 输出:
// 这是一个goroutine the second goroutine!
// 这是一个goroutine the first goroutine!
// 这是一个goroutine the first goroutine!
// 这是一个goroutine the second goroutine!
// 这是一个goroutine the second goroutine!
// over!

并发执行

​ 前文说过,并发执行就是让协程运行在同一个逻辑处理器上,我们可以在主方法中使用runtime.GOMAXPROCS(1)强制控制在一个逻辑处理器中

// main方法
func main() {
    routine.GOMAXPROCS(1)
    // 实例化计数器wg
	var wg sync.WaitGroup
    // 要运行两个goroutine函数
	wg.Add(2)
	go functions.Demo01(2, "the first goroutine!", &wg)
	go functions.Demo01(3, "the second goroutine!", &wg)
    // 等待两个goroutine运行完毕
	wg.Wait()
    // 执行完毕后再输出over!
	fmt.Println("over!")
}

​ 这样子会让处理器在两个工作中不断的切换,而并不是真正的同时进行,而真正的同进行,需要并行执行

并行执行

​ 并行执行就是让各个goroutine都在单独的逻辑处理器中运行,这时runtime.GOMAXPROCS(tmp)中的tmp参数就应给等于你的goroutine个数,同时我们可以使用runtime.NumCPU()来返回自己电脑中的物理处理器个数,而物理处理器的个数是和自己电脑的处理器挂钩的

// main方法
func main() {
    routine.GOMAXPROCS(2)
    fmt.Println(runtime.NumCPU()) // 20
    // 实例化计数器wg
	var wg sync.WaitGroup
    // 要运行两个goroutine函数
	wg.Add(2)
	go functions.Demo01(2, "the first goroutine!", &wg)
	go functions.Demo01(3, "the second goroutine!", &wg)
    // 等待两个goroutine运行完毕
	wg.Wait()
    // 执行完毕后再输出over!
	fmt.Println("over!")
}

​ 通过并行执行,我们就可以保证不同的协程是同时运行在不同的逻辑处理器当中,可以实行同时运行