golang 线程等待

发布时间:2024-07-05 00:22:13

我是一名专业的Golang开发者,今天我将为大家讲解一下Golang中的线程等待。在并发编程中,线程的等待是非常重要的一个概念。Golang提供了多种方法来实现线程的等待,接下来我将为大家详细介绍。

一、使用WaitGroup进行线程等待

在Golang中,可以使用sync包中的WaitGroup来实现线程的等待。WaitGroup是一个计数信号量,用于协调并发任务的执行。我们可以通过Add方法增加WaitGroup的计数,通过Done方法减少WaitGroup的计数,通过Wait方法等待WaitGroup的计数变为0。

以一个简单的例子来说明如何使用WaitGroup进行线程等待:

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Printf("Worker %d starting\n", id)
    // 模拟耗时操作
    for i := 0; i < 1000000000; i++ {
    }
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
    fmt.Println("All workers done")
}

在上面的代码中,我们首先创建了一个WaitGroup对象wg。然后使用for循环创建了5个worker协程,并分别给每个协程传递了WaitGroup对象的指针。

在worker函数中,我们使用defer关键字在函数执行完毕时调用了WaitGroup的Done方法,用于减少计数。在最后,我们调用了WaitGroup的Wait方法,用于等待所有协程执行完毕。

通过运行上述代码,我们可以看到输出结果中首先打印了各个协程的启动信息,然后等待所有协程执行完毕后打印了"All workers done"。

二、使用Channel进行线程等待

除了使用WaitGroup,Golang中还可以使用Channel来实现线程的等待。通过利用Channel的通信机制,我们可以在主协程中等待所有子协程执行完毕。
package main

import (
    "fmt"
    "time"
)

func worker(id int, done chan bool) {
    fmt.Printf("Worker %d starting\n", id)
    // 模拟耗时操作
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)

    // 发送信号告诉主协程我已经执行完毕了
    done <- true
}

func main() {
    numWorkers := 5
    done := make(chan bool)

    for i := 1; i <= numWorkers; i++ {
        go worker(i, done)
    }

    // 等待所有子协程执行完毕
    for i := 0; i < numWorkers; i++ {
        <-done
    }
    fmt.Println("All workers done")
}

在上述代码中,我们首先创建了一个用于接收子协程完成信号的通道done。然后使用for循环创建了5个worker协程,并分别给每个协程传递了done通道。

在worker函数中,我们在任务执行完毕后向done通道发送一个信号,告诉主协程任务已经完成。主协程通过<-done语法从通道中接收信号,并等待所有子协程都完成后输出"All workers done"。

三、使用Context进行线程等待

除了WaitGroup和Channel,Golang的Context包也提供了一种便捷的方法来实现线程等待。Context可以实现父协程等待子协程的功能,并且可以实现超时控制和取消任务等功能。
package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, id int) {
    fmt.Printf("Worker %d starting\n", id)

    // 模拟耗时操作
    time.Sleep(time.Second)
    select {
    case <-ctx.Done():
        fmt.Printf("Worker %d done (canceled)\n", id)
    default:
        fmt.Printf("Worker %d done\n", id)
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    numWorkers := 5
    for i := 1; i <= numWorkers; i++ {
        go worker(ctx, i)
    }

    time.Sleep(3 * time.Second)
    cancel()

    time.Sleep(time.Second)
}

在上述代码中,我们首先使用context.WithCancel创建了一个Context对象ctx和一个用于取消任务的cancel函数。然后使用for循环创建了5个worker协程,并分别给每个协程传递了ctx。

在worker函数中,我们使用select语句监听ctx的状态,如果收到了取消信号,则打印"Worker %d done (canceled)",否则打印"Worker %d done"。

主协程在执行完所有子协程之后,通过调用cancel函数向子协程发送取消信号。这样,所有的子协程都会接收到取消信号并结束自己的任务。

通过以上三种方法,Golang提供了多样化的线程等待机制,开发者可以根据具体需求选择最适合的方法。同时,在实际开发中,我们还可以根据具体场景结合使用WaitGroup、Channel和Context等进行线程的等待操作。这样既能保证并发任务的正确执行,又能实现更好的任务控制和管理。这就是Golang线程等待的相关知识,请大家多多练习和实践。

相关推荐