Golang中Goroutines与Channels

简介: Golang中Goroutines与Channels

前言

并发指的是同时进行多个任务的程序,Web处理请求,读写处理操作,I/O操作都可以充分利用并发增长处理速度,随着网络的普及,并发操作逐渐不可或缺


一、Goroutines

1、Goroutine的定义

在Golang中一个Goroutines就是一个执行单元,而每个程序都应该有一个主函数main也就是主Goroutines,从某种意义上你也可以把goroutine当作一个线程。


2、Goroutine的使用

Go语言中使用 goroutine 非常简单,只需要在函数或方法调用前加上go关键字就可以创建一个 goroutine,从而让该函数或方法在新创建的 goroutine 中执行。
go func


3、Goroutine的特点

在主Goroutine结束之后其他的所有Goroutine都会直接退出。例如:

import "fmt"

func main() {
   
    go getnum([]int{
   1, 5, 4, 84, 8, 4})
    fmt.Println("程序已经结束啦")

}
func getnum(s []int) {
   
    for _, v := range s {
   
        fmt.Println(v)
    }
}              
//程序已经结束啦

如果让主Gororutines睡一会

package main

import (
    "fmt"
    "time"
)

func main() {
   
    go getnum([]int{
   1, 5, 4, 84, 8, 4})
    time.Sleep(time.Second)
    fmt.Println("程序已经结束啦")

}
func getnum(s []int) {
   
    for _, v := range s {
   
        fmt.Print(v)
    }
}
//1548484程序已经结束啦

差别显而易见


4、多个Goroutine的使用

要想使用多个Goroutine可以使用到waitgroup

package main

import (
    "fmt"
    "sync"
)

// 声明全局等待组变量
var wg sync.WaitGroup

func worker(i int) {
   
    fmt.Printf("%d 开始working...\n", i)
    wg.Done() // 告知当前goroutine完成,释放资源
}

func main() {
   
    for i := 0; i <= 10; i++ {
   
        wg.Add(1) // 登记1个goroutine,开放一个进程供其使用
        go worker(i)
    }
    wg.Wait() // 阻塞等待登记的goroutine完成,不然主Go结束了就都退出了
    fmt.Println("程序结束了...")
/*
10 开始working...
2 开始working...
0 开始working...
4 开始working...
1 开始working...
7 开始working...
8 开始working...
5 开始working...
6 开始working...
9 开始working...
3 开始working...
程序结束了...  
 */
}

其中Add,wait,Done的作用在代码块中已经描述出来了。

那么如果想让每个working有序的打印那么就只需要将wg.Wait放在for循环当中就可以了
但是这样子的话仔细思考一下,它和串行还有什么区别呢?那么想要真正做到并发且有序打印应该怎么做?
学习笔记之go语句的执行规则中我有学习过


二、Channels

1、介绍

Go语言中goroutine是程序的并发体,而channel则是它们这些并发体的通信工具,它可以让不同的goroutine之间发送信息


2、 channel的使用

channel跟map类似的在使用之前都需要使用make进行初始化
ch1 := make(chan int, 5)

未初始化的channel零值默认为nil

var ch chan int
fmt.Println(ch) // <nil>

channel也拥有close方法,当channel使用结束后就可以close释放掉channel

channel缓冲区

看这个程序:

package main

import "fmt"

func main() {
   
    ch1 := make(chan int)
    ch1 <- 5
    rec := <-ch1
    fmt.Println("ch1被接受,程序结束:rec:,", rec)
}
//fatal error: all goroutines are asleep - deadlock!

由于ch1没有缓冲区,channel没有缓冲区的话:

只有在有接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的阶段。同理,如果对一个无缓冲通道执行接收操作时,没有任何向通道中发送值的操作那么也会导致接收操作阻塞。

如果想要运行成功那么在发送信息前就应该有另外的进程等待着接收

package main

import (
    "fmt"
    "time"
)

func main() {
   
    ch1 := make(chan int)
    go receive(ch1)
    ch1 <- 5
    time.Sleep(time.Second)

}
func receive(ch1 chan int) {
   
    for {
   
        select {
   
        case rec2 := <-ch1:
            fmt.Println("ch1被接受,程序结束:rec:,", rec2)
        }
    }
}
//ch1被接受,程序结束:rec:, 5

但是如果有缓冲区就能避免程序阻塞,可以将发送的channel放在缓冲区直至有接收方将它接收

3、 单向通道

<- chan int // 只接收通道,只能接收不能发送
chan <- int // 只发送通道,只能发送不能接收
而对于close方法只能是发送通道拥有

4、select与channel配合使用

Select 的使用方式类似于 switch 语句,它也有一系列 case 分支和一个默认的分支。
每个 case分支会对应一个通道的通信(接收或发送)过程。select 会一直等待,直到其中的某个 case 的通信操作完成时,就会执行该 case分支对应的语句。

具体格式如下:

select {
   
case <-ch1:
    //...
case rec := <-ch2:
    //...
case ch3 <- 10:
    //...
default:
    //默认操作
}

使用select的话

可处理一个或多个 channel 的发送/接收操作。
如果多个 case 同时满足,select 会随机选择一个执行。
对于没有 case 的 select 会一直阻塞,可用于阻塞 main 函数,防止退出

三、并发锁

1、互斥锁

使用

var tex sync.Mutex
tex.Lock()
tex.Unlock()

当一个 goroutine 获取互斥锁之后,其他的 goroutine 将等待直至解锁。

2、读写锁

使用

var tex sync.RWMutex
tex.Lock()
tex.Unlock()

读写锁分为两种:读锁和写锁。
当一个 goroutine 获取到读锁之后,其他的 goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;

而当一个 goroutine 获取写锁之后,其他的 goroutine无论是获取读锁还是写锁都会等待。


3、使用场景

如果并发过程中绝大部分都是读操作,那么使用读写锁将会优于互斥锁,如果读操作并不是特别多那将不会有太大差别


4、使用原因

多个 goroutine 同时操作一个资源(临界区)的情况,这种情况下就会发生竞态问题(数据竞态)

例如

package main

import (
    "fmt"
    "sync"
)

var (
    x int64

    wg sync.WaitGroup // 等待组
)

// add 对全局变量x执行5000次加1操作
func add() {
   
    for i := 0; i < 5000; i++ {
   
        x = x + 1
    }
    wg.Done()
}

func main() {
   
    wg.Add(2)

    go add()
    go add()

    wg.Wait()
    fmt.Println(x)
}
//结果都是小于5000的数字

为什么结果不是5000呢,因为在并发的过程有可能会出现多个go同时执行x=x+1,这样只会有一个是有效的

相关文章
|
7月前
|
监控 安全 Go
Golang深入浅出之-Channels基础:创建、发送与接收数据
【4月更文挑战第22天】Go语言的Channels是并发通信的核心,用于Goroutines间安全高效的数据交换。本文介绍了Channels的基础知识,包括创建(无缓冲和缓冲通道)、发送与接收数据,以及如何避免常见问题。创建通道使用`make(chan T)`,发送数据用`ch &lt;- value`,接收数据则相反。无缓冲通道需匹配的发送和接收操作,否则会阻塞。缓冲通道能暂存数据,但需注意缓冲区大小以防止死锁。关闭通道后不能发送,但能接收剩余数据,多值接收可检测通道状态。掌握这些概念和技巧,能提升Go语言并发编程的效率和稳定性。
56 1
|
3月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
144 4
Golang语言之管道channel快速入门篇
|
3月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
72 4
Golang语言文件操作快速入门篇
|
3月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
119 3
Golang语言之gRPC程序设计示例
|
3月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
101 4
|
3月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
55 3
|
3月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
79 4
Golang语言goroutine协程篇
|
3月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
81 3
Golang语言之Prometheus的日志模块使用案例
|
3月前
|
Go
Golang语言之函数(func)进阶篇
这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。
74 3
Golang语言之函数(func)进阶篇
|
2月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
38 0