golang中创建协程以及协程间的相互通信

 更新时间:2026年07月08日 09:16:33   作者:忍界英雄  
本文详细介绍了在Golang中如何创建协程,利用协程的轻量级特性实现并发,以及如何通过管道进行协程间的通信和同步控制,包括无缓冲和有缓冲管道的区别,以及sync.WaitGroup在进程同步中的应用

在golang中创建协程

在 Go 语言中,使用协程来实现并发模型。

协程是 Go 语言的并发执行单元,它比传统的线程更轻量级,允许我们并发执行多个任务。

Go 会在内部使用一组线程来运行创建的协程,并在这些线程之间高效地分配协程执行,这样可以在不增加太多操作系统线程的情况下执行大量的协程。

在golang中,我们可以方便的使用go func() {}() 语句用于启动一个新的协程(goroutine)。

协程是 Go 语言的并发执行单元,它比传统的线程更轻量级,允许我们并发执行多个任务。

当在函数调用前加上 go 关键字时,这个函数就会在新的协程中异步执行。

下面是一个简单的在golang主函数中创建协程的例子:

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Println(i)
        time.Sleep(time.Second)
    }
}

func main() {
    // 启动一个新的协程来执行 printNumbers 函数
    go printNumbers()

    // 主协程中执行其他工作
    for i := 'a'; i <= 'e'; i++ {
        fmt.Printf("%c", i)
        time.Sleep(time.Second)
    }

    // 等待足够的时间以确保 printNumbers 协程可以完成执行
    time.Sleep(6 * time.Second)
    fmt.Println("main function finished")
}

我们在新的协程中,启动了一个printNumbers()函数,运行结果如下:a12bc3d4e5

这说明我们成功创建了一个协程,这个协程和主程序并发运行。上述代码也可以写成下面的形式:

func main() {
    // 启动一个新的协程来执行 printNumbers 函数
    go func(){    for i := 1; i <= 5; i++ {
        fmt.Println(i)
        time.Sleep(time.Second)
    }}()

    // 主协程中执行其他工作
    for i := 'a'; i <= 'e'; i++ {
        fmt.Printf("%c", i)
        time.Sleep(time.Second)
    }

    // 等待足够的时间以确保 printNumbers 协程可以完成执行
    time.Sleep(6 * time.Second)
    fmt.Println("main function finished")
}

我们再次运行发现,运行结果如下:a1b23c45e,可以看到虽然代码是一样的,但是打印出字母和数字的顺序却不同。

这是为什么呢?因为我们只开启了一个协程,并没有控制协程和主程序的执行顺序,所以每次运行,有时协程运行的快些,有时主程序运行的快些。

那么如果我们想要打印一个字母,再打印一个数字,要怎么办呢?这就需要用到golang提供的,协程间的通信机制。

golang中进程间的通信

在gonglang中,可以使用管道来进行协程间的通信。

管道的创建和使用

通过 make(chan Type)可以创建一个管道,其中 Type是管道将传递的数据类型。我们通过通道对上述代码进行改造:

func main() {
	ch := make(chan bool)   // 用于从 printNumbers 协程到主协程的同步
	done := make(chan bool) // 用于从主协程到 printNumbers 协程的同步

	// 启动一个新的协程来执行 printNumbers 函数
	go func() {
		for i := 1; i <= 5; i++ {
			fmt.Printf("%d", i)
			ch <- true // 发送数字到主协程
			<-done     // 等待主协程的信号
		}
		close(ch) // 关闭通道
	}()

	// 主协程中执行其他工作
	for i := 'a'; i <= 'e'; i++ {
		<-ch // 等待来自 printNumbers 协程的数字
		fmt.Printf("%c", i)
		done <- true // 发送信号到 printNumbers 协程
	}

	fmt.Println("main function finished")
}

在上述代码中,我们通过通道chdone进行进程间的消息传递,主程序通过<-ch收到ch通道传来的信号后,才打印出数字。

同样,协程收到来自主程序通道传来的信号后,再继续打印字母。

这样就可以保证打印的顺序始终为1a2b3c4d5

管道通信中传递的值

在我们的代码中使用了<-ch,即没有关心通道传来的具体值,如果需要用到通道传递过来的值,可以使用a<-ch,这样通道传来的值就会被存储在变量a中。

有缓冲的管道通信

此外管道可以是无缓冲的,也可以是有缓冲的,我们上述使用的是无缓冲的通道。

无缓冲管道在发送和接收之间是同步的:发送操作会阻塞,直到另一端的协程执行接收操作,而接收操作也会阻塞,直到有数据被发送。这种同步通信机制确保了协程间的同步执行。

有缓冲的管道则允许在协程间异步传输数据。当你创建一个有缓冲的管道时,你需要指定缓冲区的大小。有缓冲的管道可以存储多个元素,直到达到其缓冲区的容量为止。发送操作只有在缓冲区满时才会阻塞,接收操作只有在缓冲区空时才会阻塞。

创建有缓冲的管道的示例:ch := make(chan int, 5)

在这个例子中,ch 是一个可以存储最多 5 个整数的有缓冲管道。这意味着我们可以在没有接收方的情况下向管道发送最多 5 个整数,而不会发生阻塞。

golang中进程间的同步控制

golang中当协程的父程序即创建协程的程序结束时,父程序中的协程会全部终止

在文章最开始的代码中,我们通过使用 time.Sleep(6 * time.Second)来确保在协程结束之前,主程序不会停止,这其实是非常不好的做法。

同步原语sync.WaitGroup

实际上,在gonglang中,一般使用WaitGroup来进行进程间的同步控制,确保在子协程全部结束时,父协程才会终止。

sync.WaitGroup 是 Go 语言中的一个同步原语,用于等待一组协程(goroutines)的完成。它主要用于确保所有协程都已经完成它们的工作。

WaitGroup中常用的三个函数:Add()、Done()和Wait()

在使用sync.WaitGroup之前我们首先要学习其Add()、Done()和Wait()函数。

wg.Add(delta int) 方法用于设置等待组中的协程数量。每次调用 wg.Add() 时,传递的参数(通常是 1)将会被加到内部计数器上。这个方法通常在启动协程之前调用,以表明有新的协程需要等待。

wg.Done() 方法用于表示一个协程完成了它的工作。每当协程完成它的执行时,它应该调用 wg.Done(),这将从内部计数器减去 1。

当内部计数器减到 0 时,任何阻塞在 wg.Wait() 调用上的协程都会停止阻塞,继续执行。

wg使用实例

通过下面这个函数我们可以确保所有子协程全部完成之后,主程序才会终止:

func main() {
	// 启动一个新的协程来执行 printNumbers 函数
	var wg sync.WaitGroup
	// 等待协程数增加一
	wg.Add(1)
	go func() {
		for i := 1; i <= 5; i++ {
			fmt.Println(i)
		}
		//协程完成等待协程数减少1
		wg.Done()
	}()

	wg.Add(1)
	go func() {
		for i := 'a'; i <= 'e'; i++ {
			fmt.Printf("%c", i)
		}
		wg.Done()
	}()

	// 等待所有的协程完成
	wg.Wait()
	fmt.Println("main function finished")
}

通过这种方法,我们发现,只有当两个子程序都运行完成后,才会打印出main function finished。

到此这篇关于golang中创建协程以及协程间的相互通信的文章就介绍到这了,更多相关golang协程间的相互通信内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang避免内存溢出的方法

    golang避免内存溢出的方法

    内存溢出(Memory Overflow)是指程序在运行时超出了分配给它的内存限制,从而导致程序异常或崩溃的现象,内存溢出的问题在任何编程语言中都可能出现,Go 语言也不例外,本文给大家介绍了golang是如何解决内存溢出的,需要的朋友可以参考下
    2024-09-09
  • Golang中goroutine和channel使用介绍深入分析

    Golang中goroutine和channel使用介绍深入分析

    一次只做一件事情并不是完成任务最快的方法,一些大的任务可以拆解成若干个小任务,goroutine可以让程序同时处理几个不同的任务,goroutine使用channel来协调它们的工作,channel允许goroutine互相发送数据并同步,这样一个goroutine就不会领先于另一个goroutine
    2023-01-01
  • 一文带你理解Golang中的Time结构

    一文带你理解Golang中的Time结构

    根据golang的time包的文档可以知道,golang的time结构中存储了两种时钟,一种是Wall Clocks,一种是Monotonic Clocks,下面我们就来简单了解一下这两种结构吧
    2023-09-09
  • 详解Go如何基于现有的context创建新的context

    详解Go如何基于现有的context创建新的context

    在 Golang 中,context 包提供了创建和管理上下文的功能,那么在GO语言中如何基于现有的context创建新的context,下面小编就来和大家详细聊聊
    2024-01-01
  • 简单聊聊Golang中Flag包的具体使用

    简单聊聊Golang中Flag包的具体使用

    flag 包是 Go 语言标准库中的一个包,用于解析命令行参数,这篇文章主要来和大家简单聊聊Golang中Flag包的具体使用,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-05-05
  • Golang小数操作指南之判断小数点位数与四舍五入

    Golang小数操作指南之判断小数点位数与四舍五入

    这篇文章主要给大家介绍了关于Golang小数操作指南之判断小数点位数与四舍五入的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-03-03
  • Golang最大递减数算法问题分析

    Golang最大递减数算法问题分析

    这篇文章主要介绍了Golang最大递减数算法问题分析,结合实例形式分析了Go语言数字遍历与运算相关操作技巧,需要的朋友可以参考下
    2017-01-01
  • Go 计时器使用示例全面讲解

    Go 计时器使用示例全面讲解

    这篇文章主要为大家介绍了Go 计时器使用示例全面讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • 盘点总结2023年Go并发库有哪些变化

    盘点总结2023年Go并发库有哪些变化

    这篇文章主要为大家介绍了2023年Go并发库的变化盘点总结,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • go语言里面实现并发安全扣减库存的几种方式小结

    go语言里面实现并发安全扣减库存的几种方式小结

    本文介绍了GORM框架中的几种高级查询技巧,包括随机返回数据的方法,锁机制的使用,Upsert操作,在gorm-gen中使用原生SQL查询这几种方法,具有一定的参考价值,感兴趣的可以了解一下
    2026-04-04

最新评论