Go语言并发基础 | 青训营

70 阅读5分钟

golang是一门天生支持并发的编程语言,它提供了丰富而简单的并发编程原语。下面是Go语言中实现并发的一些特性

  1. Goroutine(协程):Goroutine是Go语言中轻量级的执行单元,由Go运行时环境调度和管理。通过关键字go可以启动一个Goroutine,并发地执行函数或方法。
  2. Channel(通道):Channel是用于Goroutine之间进行安全的数据交换和同步的机制。通过Channel,可以在Goroutine之间发送和接收数据。Channel提供了阻塞式的操作,可以保证数据的同步和顺序性。
  3. Mutex(互斥锁):Mutex是Go语言中的互斥量,用于保护共享资源的访问。通过互斥锁,可以在不同的Goroutine之间实现对共享资源的互斥访问,防止竞态条件的发生。
  4. WaitGroup(等待组):WaitGroup用于等待一组Goroutine的完成。通过WaitGroup,可以等待所有的Goroutine执行完毕后再继续执行后续操作。

一、goroutine

Goroutine(协程)是Go语言并发编程的核心概念之一。它是一种轻量级的执行单元,由Go运行时环境(runtime)调度和管理。与传统的线程相比,Goroutine具有以下特点:

  1. 轻量级:Goroutine的创建和销毁开销非常小,可以创建成千上万个Goroutine而不会造成系统资源的浪费。
  2. 并发性:Goroutine可以同时运行在多个逻辑处理器(CPU核心)上,实现真正的并行计算。
  3. 通信机制:Goroutine之间通过通道(Channel)进行安全的数据交换和同步,避免了传统线程享内存带来的同步问题。
  4. 自动调度:Go运行时环境负责Goroutine的调度和切换,开发者无需手动管理线程池或锁,简化了并发编程的复杂性。

使用Goroutine非常简单,只需在函数或方法调用前加上关键字go即可启动一个Goroutine。例如:

func main() {
    go other() // 启动一个新的协程并并发执行函数

    // 执行其他操作
}

func other() {
    // 函数内容
}

在上述例子中,other()函数将在一个独立的Goroutine中并发执行,不会阻塞main()函数的执行。可以同时启动多个Goroutine来实现并行计算或并发处理任务。

二、channel

Channel(通道)是Go语言中用于在Goroutine之间进行安全的数据交换和同步的机制。它可以用于在不同的Goroutine之间发送和接收数据,并提供了阻塞式的操作,以保证数据的同步和顺序性。

在Go语言中,通过内置的make函数来创建一个Channel,并指定所传递数据的类型。例如,创建一个传递整数类型数据的Channel:

ch := make(chan int)

创建Channel后,可以使用箭头操作符<-进行数据的发送和接收。发送数据使用<-操作符将数据发送到Channel中,接收数据则使用<-操作符从Channel中接收数据。

以下是使用Channel进行数据交换的示例:

func main() {
    tongdao := make(chan string) // 创建一个字符串类型的通道

    //开启并发
    go func() {
        tongdao <- "Hello, World!" // 发送数据到Channel
    }()

    data := <-tongdao // 从Channel接收数据
    fmt.Println(data) // 输出:Hello, World!
}

在上述示例中,通过go关键字启动了一个新的Goroutine,在该Goroutine中将字符串"Hello, World!"发送到Channel中。然后,通过msg := <-ch语句从Channel中接收数据,并将其赋值给变量msg,最后打印出来。

Channel的特点:

  • Channel是类型安全的,只能传递指定类型的数据。
  • Channel是阻塞的,当向一个Channel发送数据时,如果没有接收方准备好接收数据,发送操作会被阻塞。同样,当从一个Channel接收数据时,如果没有发送方准备好发送数据,接收操作也会被阻塞。
  • Channel的默认行为是先进先出(FIFO),保证发送和接收的顺序性。
  • 可以通过关闭Channel来表示不再发送任何数据。接收方可以通过检查返回值的第二个参数来判断Channel是否已关闭。

使用Channel可以有效地在不同的Goroutine之间进行通信和同步,避免了竞态条件和数据冲突,使得并发编程更加安全和可靠。

需要注意的是,Goroutine之间的并发访问共享数据时,需要采取适当的同步机制,如使用通道进行数据交换、互斥锁等,以避免竞态条件和数据冲突。

三、Mutex互斥锁

互斥锁(Mutex)是一种常用的同步机制,用于在并发编程中保护共享资源的访问。它提供了一种独占的方式,确保同一时间只有一个线程可以访问被保护的代码块。

在Go语言中,可以使用sync包中的Mutex类型来创建和使用互斥锁。具体而言,可以通过调用Lock()方法来获取锁,并在需要时调用Unlock()方法来释放锁。

下面是一个例子

import (  
"sync"  
"time"  
)  
  
// 并发安全,对同一个数据同时操作导致操作丢失。加锁,lock  
var (  
    x int  
    lock sync.Mutex  
) //这里定义两个全局变量  
  
func main() {  
    x = 0  
    for i := 0; i < 5; i++ {  
        go addWithLock()  
    }  
    //休眠是为了等待协程完成它的任务  
    time.Sleep(time.Second)  
    println("withlock", x)  
    x = 0  
    //不加锁有时会出现错误结果  
    for i := 0; i < 5; i++ {  
        go addWithoutLock()  
    }  
    time.Sleep(time.Second)  
    println("withoutlock", x)  
}  
  
func addWithLock() {  
    for i := 0; i < 2000; i++ {  
        lock.Lock()  
        x = x + 1  
        lock.Unlock()  
    }  
}  
func addWithoutLock() {  
    for i := 0; i < 2000; i++ {  
        x = x + 1  
    }  
}

四、WaitGroup等待组

sync.WaitGroup是Go语言中的另一个并发原语,它用于等待一组Goroutine完成执行。

WaitGroup提供了三个方法来管理计数器:Add()、Done()和Wait()。

Add(delta int):增加计数器的值,delta为正整数表示增加的数量。 Done():减少计数器的值,相当于Add(-1)。 Wait():阻塞当前Goroutine,直到计数器归零。

下面是一个简单的示例,展示了如何使用WaitGroup等待一组Goroutine完成执行:

import (  
    "fmt"  
    "sync"  
)  
  
//WaitGroup,替代sleep实现同步  
  
func main() {  
    GoWaitGroup()  
}  
func GoWaitGroup() {  
    //引入  
    var wg sync.WaitGroup  
    //开启五个协程  
    wg.Add(5)  
    for i := 0; i < 5; i++ {  
        go func(j int) {  
            //这个方法让计数器减一,表明这个协程已经完成  
            defer wg.Done()  
            hello(j)  
        }(i)  
    }  
    //进行阻塞主协程,直到计数器为0  
    wg.Wait()  
}  
func hello(i int) {  
    println("协程" + fmt.Sprint(i))  
}