匠心精神 - 良心品质腾讯认可的专业机构-IT人的高薪实战学院

咨询电话:4000806560

Golang的神器——Goroutine详解

Golang的神器——Goroutine详解

Golang(Go)是一种开源编程语言,专为构建高效且可扩展的软件而设计。它具有快速编译、垃圾回收机制和协程等特性。其中协程(Goroutine)是Golang的重要特性之一,让我们一同来探究一下。

1.Goroutine 概念

Goroutine是一种轻量级线程,它采用协作式的方式由Golang运行时进行调度。Goroutine比线程更加轻量级,一个进程可以同时运行数千个Goroutine,而线程的数量是有限的。Goroutine不仅仅是线程的一种封装,它还提供了一些强大的功能。

2.创建 Goroutine

在Golang中,Goroutine创建非常简单。只需在函数前加上关键字"go"即可创建一个Goroutine,如下所示:

```
func main() {
    go hello()
    time.Sleep(1 * time.Second)
}

func hello() {
    fmt.Println("Hello, Goroutine!")
}
```

在上述代码中,我们使用关键字"go"创建了一个Goroutine,并在Goroutine中执行了hello()函数。我们为了让程序等待Goroutine执行完毕,使用了time.Sleep()函数,让主程序睡眠1秒钟。

3.使用 Goroutine

使用 Goroutine 可以让程序执行更高效、更快速、更可靠、更灵活。在下面的代码中,我们使用Goroutine执行了两个耗时的任务,然后使用channel进行通信。

```
func main() {
    c := make(chan string)
    go task1(c)
    go task2(c)
    r1 := <-c
    r2 := <-c
    fmt.Println(r1)
    fmt.Println(r2)
}

func task1(c chan string) {
    time.Sleep(5 * time.Second)
    c <- "Task 1 is completed."
}

func task2(c chan string) {
    time.Sleep(3 * time.Second)
    c <- "Task 2 is completed."
}
```

在上述代码中,我们首先创建了一个channel,用于在Goroutine之间进行通信。

我们使用关键字"go"创建了两个Goroutine,并在每个Goroutine中执行了耗时的任务task1()和task2(),然后将结果通过channel通信回主程序。最后,我们打印了任务的结果。

4. Golang 中的 Goroutine 调度

Golang的Goroutine是由运行时系统进行调度的,而不是由操作系统进行调度的。Golang的Goroutine调度器是一个协作式调度器,也就是说,Goroutine只有在某些事件(例如通道阻塞)发生时才会被调度。

例如,在下面的代码中,我们使用两个Goroutine打印数字并进行通信。由于channel的阻塞,Goroutine被切换并交替运行。

```
func main() {
    c := make(chan bool)
    go printNumbers(c)
    go printLetters(c)
    <-c
    <-c
}

func printNumbers(c chan bool) {
    for i := 1; i <= 100; i++ {
        fmt.Printf("%d ", i)
        if i%10 == 0 {
            fmt.Println("\nNumbers Goroutine is blocked.")
            c <- true
            fmt.Println("\nNumbers Goroutine is unblocked.")
        }
    }
}

func printLetters(c chan bool) {
    for i := 'A'; i <= 'Z'; i++ {
        fmt.Printf("%c ", i)
        if i%10 == 0 {
            fmt.Println("\nLetters Goroutine is blocked.")
            c <- true
            fmt.Println("\nLetters Goroutine is unblocked.")
        }
    }
}
```

在上述代码中,我们在两个Goroutine中分别打印数字和字母,并在每个Goroutine中使用if语句来检查是否需要通信。当某个Goroutine被阻塞时,它将通过channel通知另一个Goroutine,从而完成Goroutine之间的交替执行。

5. Goroutine 的并发安全

在使用Goroutine时,需要注意并发安全的问题。在Golang中,可以通过使用互斥锁(mutex)来解决并发访问共享变量的问题。

在下面的代码中,我们使用两个Goroutine对共享变量进行修改,但由于没有使用互斥锁,这会导致竞态条件,并且输出结果是不确定的。

```
func main() {
    counter := 0

    go increment(&counter)
    go increment(&counter)

    time.Sleep(1 * time.Second)
    fmt.Println("Counter:", counter)
}

func increment(counter *int) {
    for i := 0; i < 5; i++ {
        *counter++
        fmt.Println(*counter)
    }
}
```

在上述代码中,我们使用两个Goroutine对counter变量进行增加,并在每次增加后打印counter的值。然而,由于两个Goroutine同时访问了counter变量,这会导致输出结果的顺序不确定和不一致。

为了解决这个问题,我们需要使用mutex。在下面的代码中,我们使用sync包中的mutex来保证并发安全。

```
func main() {
    var mutex sync.Mutex
    counter := 0

    go increment(&counter, &mutex)
    go increment(&counter, &mutex)

    time.Sleep(1 * time.Second)
    fmt.Println("Counter:", counter)
}

func increment(counter *int, mutex *sync.Mutex) {
    for i := 0; i < 5; i++ {
        mutex.Lock()
        *counter++
        fmt.Println(*counter)
        mutex.Unlock()
    }
}
```

在上述代码中,我们首先创建了一个mutex对象,然后使用mutex.Lock()函数来获取锁,阻止其他Goroutine访问共享变量。在修改完成后,我们使用mutex.Unlock()函数来释放锁,允许其他Goroutine访问共享变量。

总结

Golang的Goroutine是一种非常强大的特性,它让我们的程序更加高效、灵活、可靠、并发安全。在使用Goroutine时,需要注意这些细节和注意事项,才能更好地发挥Goroutine的优势,并编写出高质量的Golang程序。