> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Golang High-Performance Programming EP6 : Tips For Asynchronous Programming
- URL: https://huizhou92.com/golang-high-performance-programming-ep6-tips-for-asynchronous-programming/
- Published: 2024-07-22T08:47:32.000Z
- Updated: 2026-09-06T11:40:07.000Z
- Author: huizhou92
- Tags: tech, go, #lang-en

### Introduction

The primary mission of Golang is to simplify asynchronous programming. When faced with operations requiring batch processing and lengthy execution times, traditional single-threaded execution becomes cumbersome, prompting the need for asynchronous parallel processing. This article introduces some tips for asynchronous programming in Golang.

> This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on [Medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you very much.

Let's start by introducing a library that simplifies concurrent programming: [conc](https://github.com/sourcegraph/conc?ref=huizhou92.com). It encapsulates many useful tools, such as [WaitGroup](https://pkg.go.dev/github.com/sourcegraph/conc?ref=huizhou92.com#WaitGroup) and [iter.Map](https://pkg.go.dev/github.com/sourcegraph/conc/iter?ref=huizhou92.com#Map). While we may not necessarily use `conc` in production code, learning from its concepts is still beneficial.

### Usage

#### Using `go`

The simplest and most common method: use the `go` keyword.

```go
func main() {  
 go func() {  
  fmt.Println("hello world1")  
 }()  
 go func() {  
  fmt.Println("hello world2")  
 }()  
}

```

Or:

```go
func main() {  
 go Announce("hello world1")  
 go Announce("hello world2")  
}  
func Announce(message string) {  
 fmt.Println(message)  
}

```

Using anonymous functions to pass parameters:

```go
data := "Hello, World!"  
go func(msg string) {  
 // Use msg to perform asynchronous task logic processing
 fmt.Println(msg)  
}(data)

```

This method doesn't require consideration of return values. If return values are needed, the following method can be used.

#### Implementing Timeout Control with Goroutines and Channels

```go
ch := make(chan int, 1)  
timer := time.NewTimer(time.Second)  
go func() {  
    time.Sleep(2 * time.Second)  
    ch <- 1  
    close(ch)  
}()  
select {  
case <-timer.C:  
    fmt.Println("timeout")  
case result := <-ch:  
    fmt.Println(result)  
}

```

#### Using `sync.WaitGroup`

`sync.WaitGroup` is used to wait for a collection of goroutines to finish their tasks. The `Add()` method increases the number of goroutines to wait for, the `Done()` method marks a goroutine as completed, and the `Wait()` method blocks until all goroutines are finished.

```go
var wg sync.WaitGroup  
// Start multiple goroutines  
for i := 0; i < 5; i++ {  
   wg.Add(1)  
   go func(index int) {  
      defer wg.Done()  
      // Asynchronous task logic  
   }(i)  
}  
wg.Wait()

```

#### Error Handling with `errgroup` for Goroutine Groups

The `errgroup` package is useful for easily capturing errors from goroutines. It’s a utility in the Go standard library for managing a group of goroutines and handling their errors.

```go
var eg errgroup.Group  
for i := 0; i < 5; i++ {  
    eg.Go(func() error {  
     return errors.New("error")  
    })  

    eg.Go(func() error {  
     return nil  
    })  
}  

if err := eg.Wait(); err != nil {  
    // Handle error  
}

```

### Tips and Techniques

#### Using Range and Close Operations with Channels

The range can be used to iterate over the values received on a channel until the channel is closed. Use the `close` function to close the channel, signaling no more values will be sent.

```go
ch := make(chan int)  

go func() {  
    for i := 0; i < 5; i++ {  
        ch <- i // Send values to the channel  
    }  
    close(ch) // Close the channel  
}()  

// Use range to iterate over received values
for val := range ch {  
    // Process received values  
}

```

#### Waiting Multiple Goroutines With Select

```go
ch1 := make(chan int)  
ch2 := make(chan string)  

go func() {  
    // Asynchronous task 1 logic  
    ch1 <- result1  
}()  

go func() {  
    // Asynchronous task 2 logic  
    ch2 <- result2  
}()  

// Wait for multiple asynchronous tasks to complete in the main goroutine
select {  
case res1 := <-ch1:  
    // Process result 1  
case res2 := <-ch2:  
    // Process result 2  
}

```

#### Implementing Timeout Control with Select and `time.After()`

If you need to set a timeout for asynchronous operations, you can use the select statement in conjunction with the `time.After()` function.

```go
ch := make(chan int)  

go func() {  
    // Asynchronous task logic  
    time.Sleep(2 * time.Second)  
    ch <- result  
}()  

// Set a timeout
select {  
case res := <-ch:  
    // Process result  
case <-time.After(3 * time.Second):  
    // Handle timeout  
}

```

#### Using `time.Tick()` and `time.After()` for Timed Operations

The `time.Tick()` function returns a channel that sends time values periodically, useful for executing timed operations. The `time.After()` function returns a channel that sends a time value after a specified duration.

```go
tick := time.Tick(1 * time.Second) // Execute an operation every second  

for {  
    select {  
    case <-tick:  
        // Perform timed operation  
    }  
}  

select {  
case <-time.After(5 * time.Second):  
    // Execute operation after 5 seconds  
}

```

#### Using `sync.Mutex` or `sync.RWMutex` for Concurrent Safe Access

When multiple goroutines concurrently access shared data, it’s essential to ensure data access safety. `sync.Mutex` and `sync.RWMutex` provide mutual exclusion locks and read-write locks for locking before accessing shared resources, preventing data races.

```go
var mutex sync.Mutex  
var data int  

// Write operation protected by a mutex
mutex.Lock()  
data = 123  
mutex.Unlock()  

// Read operation protected by a read lock
mutex.RLock()  
value := data  
mutex.RUnlock()  

var rwMutex sync.RWMutex  
var sharedData map[string]string  

// Read operation protected by a read lock
func readData(key string) string {  
    rwMutex.RLock()  
    defer rwMutex.RUnlock()  
    return sharedData[key]  
}  

// Write operation protected by a write lock
func writeData(key, value string) {  
    rwMutex.Lock()  
    defer rwMutex.Unlock()  
    sharedData[key] = value  
}

```

Note: `sync.Mutex` locks cannot be nested. `sync.RWMutex` read locks (`RLock()`) can be nested if there are no write locks, and multiple read locks can be acquired.

#### Using `sync.Cond` for Conditional Variable Control

`sync.Cond` is a conditional variable used for communication and synchronization between goroutines. It can block and wait until a specified condition is met, then wake up waiting goroutines when the condition is satisfied.

```go
var cond = sync.NewCond(&sync.Mutex{})  
var ready bool  

go func() {  
    // Asynchronous task logic  
    ready = true  

    // Notify waiting goroutines that the condition is met
    cond.Broadcast()  
}()  

// Wait for the condition to be met
cond.L.Lock()  
for !ready {  
    cond.Wait()  
}  
cond.L.Unlock()

```

#### Managing Object Pools with `sync.Pool`

`sync.Pool` is an object pool for caching and reusing temporary objects, improving allocation and recycling efficiency.

```go
type MyObject struct {  
    // Object structure  
}  

var objectPool = sync.Pool{  
    New: func() interface{} {  
        // Create a new object  
        return &MyObject{}  
    },  
}  

// Get an object from the pool
obj := objectPool.Get().(*MyObject)  

// Use the object  

// Put the object back into the pool
objectPool.Put(obj)

```

#### Ensuring One-Time Execution with `sync.Once`

`sync.Once` ensures that an operation is executed only once, regardless of how many goroutines attempt to execute it. It's commonly used for initialization or loading resources.

```go
var once sync.Once  
var resource *Resource  

func getResource() *Resource {  
    once.Do(func() {  
        // Perform resource initialization, executed only once
        resource = initResource()  
    })  
    return resource  
}  

// Get the resource in multiple goroutines
go func() {  
    res := getResource()  
    // Use the resource  
}()  

go func() {  
    res := getResource()  
    // Use the resource  
}()

```

#### Resource Cleanup with `sync.Once` and `context.Context`

Combine `sync.Once` and `context.Context` to ensure a resource cleanup operation is executed only once across multiple goroutines, triggered on cancellation or timeout.

```go
var once sync.Once  

func cleanup() {  
    // Perform resource cleanup  
}  

func doTask(ctx context.Context) {  
    go func() {  
        select {  
        case <-ctx.Done():  
            once.Do(cleanup) // Execute resource cleanup only once
        }  
    }()  

    // Asynchronous task logic
}

```

#### Concurrent Safe Maps with `sync.Map`

`sync.Map` is a concurrent-safe map type in the

Go standard library, enabling safe read and write operations across multiple goroutines.

```go
var m sync.Map  

// Store key-value pairs
m.Store("key", "value")  

// Retrieve values
if val, ok := m.Load("key"); ok {  
    // Use the value  
}  

// Delete a key
m.Delete("key")

```

#### Managing Goroutines and Cancellation with `context.Context`

`context.Context` is used to pass context information between goroutines and can be used for cancellation or timeout control. Use `context.WithCancel()` to create a cancellable context, and `context.WithTimeout()` to create a context with a timeout.

```go
ctx, cancel := context.WithCancel(context.Background())  

go func() {  
    // Asynchronous task logic  
    if someCondition {  
        cancel() // Cancel the task  
    }  
}()  

// Wait for the task to complete or be canceled
select {  
case <-ctx.Done():  
    // Task canceled or timed out  
}

```

#### Setting Deadlines with `context.WithDeadline()` and `context.WithTimeout()`

`context.WithDeadline()` and `context.WithTimeout()` functions create contexts with deadlines to limit the execution time of asynchronous tasks.

```go
func doTask(ctx context.Context) {  
    // Asynchronous task logic  

    select {  
    case <-time.After(5 * time.Second):  
        // Handle timeout  
    case <-ctx.Done():  
        // Handle context cancellation  
    }  
}  

func main() {  
    ctx := context.Background()  
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)  
    defer cancel()  

    go doTask(ctx)  

    // Continue with other operations  
}

```

#### Passing Context Values with `context.WithValue()`

`context.WithValue()` allows passing key-value pairs within a context, enabling sharing and passing context-related values between goroutines.

```go
type keyContextValue string  

func doTask(ctx context.Context) {  
    if val := ctx.Value(keyContextValue("key")); val != nil {  
        // Use the context value  
    }  
}  

func main() {  
    ctx := context.WithValue(context.Background(), keyContextValue("key"), "value")  
    go doTask(ctx)  

    // Continue with other operations  
}

```

#### Atomic Operations with the `atomic` Package

The `atomic` package provides functions for atomic operations, ensuring atomicity in read and write operations on shared variables in concurrent environments.

```go
var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            increment()
        }()
    }
    wg.Wait()
    fmt.Println("Counter:", counter)
}

```

### Summary

In this article, we introduce some commonly used keywords. Mastering these keywords will make it easy to deal with common concurrent programming. I believe you have also discovered that the go source code does not provide the commonly used `Barrier` function in `java` and `c#`. Although we can also implement the `Barrier` function using basic concurrent statements, it is not as convenient as calling the ready-made API interface. There is `SingleFlight` in `golang.org/x` and the third-party `CyclicBarrier`. In the next article, we will introduce these two concurrent primitives.