Man is a highly concurrent species.

I met

The first impression of the Go language is that it inherently supports concurrent programming, uses coroutines, and is much lighter than threads.

On the differences between processes, threads, and coroutines

  • A process is “an instance of program execution” and acts as an entity that allocates system resources. Process creation must allocate a complete independent address space. Process switching occurs only in kernel mode.
  • Thread: A thread is an execution flow of a process, independently executing its own program code. It is the smallest unit of the program execution flow and the basic unit of processor scheduling and dispatching. A process can have one or more threads.
  • Coroutines: Coroutines are not processes or threads, and execute more like subroutines, or function calls with no return value. Concurrency coroutines can be created at the language level and then coded to manage them. Go contracts this step out to make it cheaper to run coroutines concurrently.

Go implements the simplest concurrency

for i := 0; i < 10; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}
Copy the code

The project practice

I recently had a project that needed to call multiple jobs at the same time and wait for the jobs to complete before proceeding.

Serial execution job

To start, we have a method that executes jobs and executes all jobs sequentially:

func buildJob(name string){... } buildJob("A")
buildJob("B")
buildJob("C")
Copy the code

Parallel execution job

Because all jobs can be executed concurrently, you do not have to wait for the execution of the previous job to complete before executing other jobs. We can use the Go keyword to quickly enable a goroutine, and we will execute three jobs concurrently:

go buildJob("A")
go buildJob("B")
go buildJob("C")
Copy the code

Wait for all jobs to complete

To know if each job has completed, use channel to communicate and select to check the result:

func buildJob(ch chan error, name string) {
    var err error
    
    ... // build job
    
    ch <- err // finnaly, send the result into channel
}

func build(a) error {
    jobCount := 3
    errCh := make(err chan error, jobCount)
    defer close(errCh) / / close the channel

    go buildJob(errCh, "A")
    go buildJob(errCh, "B")
    go buildJob(errCh, "C")

    for {
        select {
        case err := <-ch:
            iferr ! =nil {
                return err
            }
        }
        
        jobCount--
        if jobCount <= 0 {
            break}}return nil
}
Copy the code

Found the problem

When job A fails, the build method returns err and runs close(errCh). However, the other two jobs B and C may not have been executed at this time, and the results will also be sent to errCh. However, since errCh has been closed at this time, the program will exit panic: send on Closed channel.

Optimize the code

When sending data to a channel, we can use the second value of the received data to determine whether the channel is closed:

func buildJob(ch chan error, name string) {
    var err error
    
    ... // build job
    
    if_, ok := <-ch; ! ok {return
    }
    ch <- err // finnaly, send the result into channel
}

func build(a) error {
    jobCount := 3
    errCh := make(err chan error, jobCount)
    defer close(errCh) / / close the channel

    go buildJob(errCh, "A")
    go buildJob(errCh, "B")
    go buildJob(errCh, "C")

    for {
        select {
        case err := <-ch:
            iferr ! =nil {
                return err
            }
        }
        
        jobCount--
        if jobCount <= 0 {
            break}}return nil
}
Copy the code

conclusion

Go concurrent programming seems to only need a keyword Go to run a Goroutine, but in real practice, there are still need to deal with the problem.

Original link: k8scat.com/posts/code-…