Go Concurrency Control

Shared memory concurrency

Or may be implemented using sync.Mutex sync.RWMutex in go
mutex: sync.Mutex
sync.RWMutex: a special type of lock which allows parallel execution of a plurality of read operations, the write operation but completely exclusive.
sync.WaitGroup: A special counter, when the counter is required so that safe operation and to provide a plurality of goroutine method for providing reduced to zero before it has been waiting for.

package share_mem

import (
    "sync"
    "testing"
    "time"
)

// 非线程安全
func TestCounter(t *testing.T) {
    counter := 0
    for i := 0; i < 5000; i++ {
        go func() {
            counter++
        }()
    }
    time.Sleep(1 * time.Second)
    t.Logf("counter = %d", counter)
}

// 线程安全
func TestCounterThreadSafe(t *testing.T) {
    var mut sync.Mutex
    counter := 0
    for i := 0; i < 5000; i++ {
        go func() {
            // 释放锁的匿名函数
            defer func() {
                mut.Unlock()
            }()
            // 加锁
            mut.Lock()
            counter++
        }()
    }
    time.Sleep(1 * time.Second) // 防止主协程运行过快,子协程还没有运行完就结束了
    t.Logf("counter = %d", counter)
}

// 线程安全及等待子 goroutine 运行结束
func TestCounterWaitGroup(t *testing.T) {
    var mut sync.Mutex    // 用于控制counter在主和子协程中的竞争
    var wg sync.WaitGroup // 用的等待子 goroutine 执行结束,相当于 python 中的join
    counter := 0
    for i := 0; i < 5000; i++ {
        wg.Add(1) // +1
        go func() {
            defer func() {
                mut.Unlock()
            }()
            mut.Lock()
            counter++
            wg.Done() // -1
        }()
    }
    wg.Wait()
    t.Logf("counter=%d", counter)
}

Concurrent CSP

CSP (Communicating Sequential Processes data communication processes) is dependent on the passage means (channel) to complete the coordination between communicating entities (Process)
the CSP in the channel is the first-class objects, it is not concerned with the entity sending the message, and transmit the message when attention use the channel.
go CSP language abstract of the channel and Process (go corresponding goroutine), the underlying implementation does not matter.
Therefore, any use goroutine and implementation of concurrent channel can be classified into concurrent CSP

csp vs Actor

  • And direct communication Actor different, CSP model is to communicate through the Channel, some of the more loosely coupled
  • Go the channel has a limited capacity and independently of the process Goroutines; and such as Erlang, mailbox capacity Actor pattern is infinite, passively receiving process always process the message.

Asynchronous return results

Asynchronous returns go with goroutine + channel can be achieved, the results of our original function returned into the channel, before returning to get the results, we can do some other things in the channel.
It can be determined by the following test code, buffer channel more suitable for server-side development.

package async_test

import (
    "fmt"
    "testing"
    "time"
)

func service() string {
    time.Sleep(time.Millisecond * 50)
    return "Done"
}

func otherTask() {
    fmt.Println("Working on something else")
    time.Sleep(time.Millisecond * 100)
    fmt.Println("Task is done.")
}

// 串行
func TestService(t *testing.T) {
    fmt.Println(service())
    otherTask()
}

// 异步执行
// 将 service 的结果放入 channel, 并将 channel 返回
func AsyncService() chan string {
    // retCh := make(chan string) // 传值端和接收端都会卡住
    retCh := make(chan string, 1) // buffer channel,和有限长度的队列是一个道理
    // 开启一个goroutine
    go func() {
        ret := service()
        fmt.Println("returned result.")
        retCh <- ret
        fmt.Println("service exited.")
    }()
    return retCh
}

func TestAsyncService(t *testing.T) {
    retCh := AsyncService()
    otherTask()
    fmt.Println(<-retCh)
    // time.Sleep(time.Second * 1) // 这里是为了让非缓存 channel 可以打印"service exited."
}

Multiplexing and timeout control

When we need to receive messages from multiple channel, the multiplexer is necessary to use select.
Note: If more than one case at the same time is ready, select randomly select an execution, so to ensure that each channel has an equal chance of being the select.

package async_test

import (
    "fmt"
    "testing"
    "time"
)

func service() string {
    time.Sleep(time.Millisecond * 50)
    return "Done"
}

func otherTask() {
    fmt.Println("Working on something else")
    time.Sleep(time.Millisecond * 100)
    fmt.Println("Task is done.")
}

// 异步执行
// 将 service 的结果放入 channel, 并将 channel 返回
func AsyncService() chan string {
    // retCh := make(chan string) // 传值端和接收端都会卡住
    retCh := make(chan string, 1) // buffer channel,和有限长度的队列是一个道理
    // 开启一个goroutine
    go func() {
        ret := service()
        fmt.Println("returned result.")
        retCh <- ret
        fmt.Println("service exited.")
    }()
    return retCh
}

func TestSelect(t *testing.T) {
    select {
    case ret := <-AsyncService():
        t.Log(ret)
    case <-time.After(time.Millisecond * 100):
        t.Error("time out")
    }
}

and broadcast channel closure

When we use a multiple producers and consumers interact, we can broadcast to tell all consumers through close (channel) to stop spending.

  • Close to the channel to transmit data, it will cause panic
  • v, ok <-ch; ok as a bool value, true indicates normal acceptance, false indicates channel is closed
  • All channel receivers are turned off when the channel immediately returns to wait ok from the blocking value is false. The broadcast mechanism is often utilized to transmit signals simultaneously to multiple subscribers. Such as: exit signal.
package channel_close

import (
    "fmt"
    "sync"
    "testing"
)

func dataProducer(ch chan int, wg *sync.WaitGroup) {
    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
        }
        close(ch)
        wg.Done()
    }()
}

func dataReceiver(ch chan int, wg *sync.WaitGroup) {
    go func() {
        for {
            if data, ok := <-ch; ok {
                fmt.Println(data)
            } else {
                break
            }
        }
        wg.Done()
    }()
}

func TestChannel(t *testing.T) {
    var wg sync.WaitGroup
    ch := make(chan int)
    wg.Add(1)
    dataProducer(ch, &wg)
    wg.Add(1)
    dataReceiver(ch, &wg)
    wg.Add(1)
    dataReceiver(ch, &wg)
    wg.Add(1)
    dataReceiver(ch, &wg)
    wg.Wait()
}

Mission canceled

If we open the lot goroutine, requires the abolition of all when you can have two ways: through shared variables cancel, cancel the broadcast by select + channel

package cancel_by_close

import (
    "fmt"
    "testing"
    "time"
)

var cancelFlag bool

// 通过共享变量取消
func TestCancel1(t *testing.T)  {
    for i := 0; i < 5; i++ {
        go func(i int) {
            for {
                if cancelFlag {
                    break
                }
            }
            fmt.Println(i, "Cancelled")
        }(i)
    }
    cancelFlag = true
    time.Sleep(time.Millisecond * 10)
}

func isCancelled(cancelChan chan struct{}) bool {
    select {
    case <-cancelChan:
        return true
    default:
        return false
    }
}

func cancel1(cancelChan chan struct{}) {
    cancelChan <- struct{}{}
}

func cancel2(cancelChan chan struct{}) {
    close(cancelChan)
}

// 通过 select + channel 广播取消
func TestCancel(t *testing.T) {
    cancelChan := make(chan struct{}, 0)
    for i := 0; i < 5; i++ {
        go func(i int, cancelCh chan struct{}) {
            for {
                if isCancelled(cancelCh) {
                    break
                }
            }
            fmt.Println(i, "Cancelled")
        }(i, cancelChan)
    }
    //cancel1(cancelChan) // 只会取消其中一个任务
    cancel2(cancelChan) // 所有任务全部取消
    time.Sleep(time.Millisecond * 10)
}

Associated tasks cancellation

When we started subtasks has launched a task grandson has launched a task cancel this association was the grandson of the task we have to use context

  • Root node needs to be created by context.Background ()
  • New child node need to create his parent, the child nodes of the root node to create a way of example: ctx, cancel: = context.WithCancel (context.Background ())
  • When the current Context is canceled, based on his sub-context will be canceled
  • Receiving a cancellation notification <-ctx.Done ()
package ctx_test

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

func isCancelled(ctx context.Context) bool {
    select {
    case <-ctx.Done():
        return true
    default:
        return false
    }
}

func TestCancel(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    for i := 0; i < 5; i++ {
        go func(i int, ctx context.Context) {
            for {
                if isCancelled(ctx) {
                    break
                }
                time.Sleep(time.Millisecond * 5)
            }
            fmt.Println(i, "Cancelled")
        }(i, ctx)
    }
    cancel()
    time.Sleep(time.Second * 1)
}

Guess you like

Origin www.cnblogs.com/wuyongqiang/p/12125249.html