Golang并发编程-协程goroutine初体验

文章目录


前言

学习Golang一段时间了,一直没有使用过goroutine来提高程序执行效率,在一些特殊场景下,还是有必须开启协程提升体验的,打算整理几篇关于协程的原理的文章和案例,结合工作场景将协程使用起来。


一、Goroutine适合的使用场景

并发执行任务 : Goroutine 可用于同时执行多个任务,提高程序的性能。
非阻塞 I/O 操作 : 在进行 I/O 操作时,可以使用 goroutine 确保其他任务继续执行,而不是同步等待 I/O 完成。
事件驱动编程 : Goroutine 可用于处理事件,如监听 HTTP 请求、处理用户输入等。
并发算法 : 实现一些需要并行计算的算法,通过 goroutine 可以更轻松地管理并发执行的部分。
定时任务: 使用 goroutine 和定时器可以实现定时执行的任务。

二、Goroutine的使用

1. 协程初体验

一个 Go 程序的入口通常是 main 函数,程序启动后,main 函数最先运行,我们称之为 main goroutine。

在 main 中或者其下调用的代码中才可以使用 go + func() 的方法来启动协程。

main 的地位相当于主线程,当 main 函数执行完成后,这个线程也就终结了,其下的运行着的所有协程也不管代码是不是还在跑,也得乖乖退出。

go 复制代码
package main

import "fmt"

func mytest() {
	fmt.Println("hello, go")
}

func main() {
	// 启动一个协程
	go mytest()
	fmt.Println("hello, world")
}

因此上面这段代码运行完,只会输出 hello, world ,而不会输出hello, go(因为协程的创建需要时间,当 hello, world打印后,协程还没来得及并执行)

当我在代码中加入一行 time.Sleep 输出就符合预期了。

go 复制代码
package main

import (
	"fmt"
	"time"
)

func mytest() {
	fmt.Println("hello, go")
}

func main() {
	// 启动一个协程
	go mytest()
	fmt.Println("hello, world")
	time.Sleep(time.Second)
}

输出结果对比如下:

bash 复制代码
[root@work day01]# go run main.go 
hello, world
[root@work day01]# go run main.go 
hello, world
[root@work day01]# go run main.go 
hello, world
hello, go
[root@work day01]# 

三、WaitGroup

在上面的例子部分,为了保证 main goroutine 在所有的 goroutine 都执行完毕后再退出,我使用了 time.Sleep 这种简单的方式。这种方式在demo程序是可以接受的。但是当实际开发过程中,不同场景下,Sleep 多少时间呢,是无法预测的。

因此,Sleep这种方式还是尽量不要用了,下面介绍下sync包提供的WaitGroup类型。

WaitGroup 案例一

代码如下(示例):

go 复制代码
package main

import (
	"fmt"
	"sync"
)

func printNumbers(wg *sync.WaitGroup) {
	defer wg.Done() // 当某个子协程完成后,可调用此方法,会从计数器上减一,通常可以使用 defer 来调用。
	for i := 1; i <= 5; i++ {
		fmt.Printf("%d ", i)
	}
}

func printLetters(wg *sync.WaitGroup) {
	defer wg.Done() // 当某个子协程完成后,可调用此方法,会从计数器上减一,通常可以使用 defer 来调用。
	for char := 'a'; char <= 'e'; char++ {
		fmt.Printf("%c ", char)
	}
}

func main() {
	var wg sync.WaitGroup   

	wg.Add(2) // 初始值为0,你传入的值会往计数器上加,这里直接传入你子协程的数量
	go printNumbers(&wg)
	go printLetters(&wg)

	wg.Wait() // 阻塞当前协程,直到实例里的计数器归零。
}

结果如下:

bash 复制代码
[root@work day01]# go run main2.go 
a b c d e 1 2 3 4 5

WaitGroup 案例二

go 复制代码
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"sync"
)

func fetch(url string, wg *sync.WaitGroup) {
	defer wg.Done()

	response, err := http.Get(url)
	if err != nil {
		fmt.Printf("Error fetching %s: %v\n", url, err)
		return
	}
	defer response.Body.Close()

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		fmt.Printf("Error reading response body from %s: %v\n", url, err)
		return
	}

	fmt.Printf("Length of %s: %d\n", url, len(body))
}

func main() {
	var wg sync.WaitGroup

	urls := []string{"https://www.baidu.com", "https://cloud.tencent.com/", "https://www.qq.com/"}

	for _, url := range urls {
		wg.Add(1)
		go fetch(url, &wg)
	}
	wg.Wait()
}

输出结果如下:

bash 复制代码
[root@work day01]# go run main3.go 
Length of https://www.qq.com/: 328
Length of https://www.baidu.com: 2443
Length of https://cloud.tencent.com/: 235026

总结

本节内容,介绍了Goroutine的使用,为了保证 main goroutine 在所有的 goroutine 都执行完毕后再退出,我们又学习了WaitGroup。目前呢,因为我们没有任何的数据交换,仅仅是开启协程执行并发的任务,因此没有用到信道。后面遇到复杂一些并发场景,我们的goroutine通信就要用到信道的概念。这里我们下一节介绍。

相关推荐
用余生去守护14 分钟前
python报错系列(16)--pyinstaller ????????
开发语言·python
数据小爬虫@18 分钟前
利用Python爬虫快速获取商品历史价格信息
开发语言·爬虫·python
向宇it21 分钟前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
莫名其妙小饼干37 分钟前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
十年一梦实验室1 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
最爱番茄味1 小时前
Python实例之函数基础打卡篇
开发语言·python
Oneforlove_twoforjob2 小时前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
engchina2 小时前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
向宇it2 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
诚丞成2 小时前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++