A CAS operation scenario of go

Time:2020-8-22

About a year ago, there was such a problem:
If there are n routines executed concurrently in the program, they will write data to a channel of size n. The n routines have high concurrency and large load, so they do not want to be stuck when writing data, so this code is used.

if len(c) < n {
    C < - something // write
}

The original meaning is to ensure that it can be written to prevent the worker routinee from getting stuck. However, during the actual operation, the routine card is in the channel writing position. The reason is very simple

Simultaneous judgment of multiple routineslen(c)If the channel is not full and the code written to the channel is entered at the same time, if the processing is not timely when the channel is full, then the routine written later will be blocked here.

Use async.MutexIt is certainly possible to protect the length of the check and the code written to the channel. However, since the mutex may affect the performance, a comparison is actually usedlowTo solve the problem.

const (
    _CHAN_SIZE  = 10
    _GUARD_SIZE = 10
)

var c chan interface{} = make(_ CHAN_ SIZE + _ GUARD_ Size) // an extra block of protected space is allocated.

func write(val interface{}) {
    if len(c) < _CHAN_SIZE {
        c <- val
    }
}

Among multiple routines R1, R2… RN that are executed concurrently, only one routine is allowed to perform a certain operation at the same time, and other routines need to know that they are not authorized to operate and return, CAS operation can be used.

For these workers routines, the situation is like this:

No one else takes the place of us

The more elegant way is to use the go standard libraryatomic.CompareAndSwapThis family of functions.

// CompareAndSwapInt64 executes the compare-and-swap operation for an int64 value.
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
...

These functions function simply when the value of a given address andoldWhen it is equal, it is set to the new value and returnstrue, otherwise returnfalse
This function is atomic.

Description on Wikipedia:
Compare and swap (CAS)

So the above code can be written as follows:

func writeMsgWithCASCheck(val interface{}) {
    if atomic.CompareAndSwapInt64(&flag, 0, 1) {
        if len(c) < _CHAN_SIZE {
            c <- val
            atomic.StoreInt64(&obj.flag, 0)
            return nil
        }
        atomic.StoreInt64(&obj.flag, 0)
    }
}

If you want to ensure that it must be written in, you can set a for: outside atomic

func writeMsgWithCASCheck(val interface{}) {
    for {
        if atomic.CompareAndSwapInt64(&flag, 0, 1) {
            if len(c) < _CHAN_SIZE {
                ...
        }
    }
}

But this kind of effect and directly stuck inc <- valAgain, the CPU is full (busy, etc.).

In view of this situation, I wrote a simple test program:

$ go run cas.go
R(0)+1 R(0)+1 R(0)+1 R(0)+1 R(0)+1 R(0)+1 R(0)+1 R(2)+1 R(3)+1 R(1)+1 R(0)+1 R(1)+1 R(2)+1 R(3)+1 Chan overflow, len: 13.
quit.
$ go run cas.go cas
R(0)+1 R(0)+1 R(0)+1 R(0)+1 R(0)+1 R(0)+1 R(0)+1 R(3)+1 R(1)+1 R(2)+1 R(1)+1 R(0)+1 R(3)+1 R(2)+1 R(1)+1 R(3)+1 R(3)+1 R(3)+1 R(3)+1 R(1)+1 R(2)+1 R(2)+1 R(2)+1 R(3)+1 R(1)+1 R(2)+1 R(3)+1 R(1)+1 R(1)+1 R(2)+1 R(1)+1 R(2)+1 <nil>
quit.

When four routines are opened to write continuously, it is easy to write more than the expected size.

The complete code is as followscas.go

package main

import (
    "errors"
    "fmt"
    "os"
    "sync/atomic"
    "time"
)

const (
    _CHAN_SIZE  = 10
    _GUARD_SIZE = 10

    _TEST_CNT = 32
)

type Obj struct {
    flag int64
    c    chan interface{}
}

func (obj *Obj) readLoop() error {
    counter := _TEST_CNT
    for {
        time.Sleep(5 * time.Millisecond)
        if len(obj.c) > _CHAN_SIZE {
            return errors.New(fmt.Sprintf("Chan overflow, len: %v.", len(obj.c)))
        } else if len(obj.c) > 0 {
            <-obj.c
            counter--
        }
        if counter <= 0 {
            return nil
        }
    }
}

func (obj *Obj) writeMsg(idx int, v interface{}) (err error) {
    for {
        if len(obj.c) < _CHAN_SIZE {
            obj.c <- v
            fmt.Printf("R(%v)+1 ", idx)
            return nil
        }
    }
}

func (obj *Obj) writeMsgWithCASCheck(idx int, v interface{}) (err error) {
    for {
        if atomic.CompareAndSwapInt64(&obj.flag, 0, 1) {
            if len(obj.c) < _CHAN_SIZE {
                obj.c <- v
                atomic.StoreInt64(&obj.flag, 0)
                fmt.Printf("R(%v)+1 ", idx)
                return nil
            } else {
                atomic.StoreInt64(&obj.flag, 0)
            }
        }
    }

    return nil
}

func main() {
    useCAS := false
    if len(os.Args) > 1 && os.Args[1] == "cas" {
        useCAS = true
    }
    routineCnt := 4
    tryCnt := _TEST_CNT / routineCnt
    var obj = &Obj{c: make(chan interface{}, _CHAN_SIZE+_GUARD_SIZE)}

    for idx := 0; idx < routineCnt; idx++ {
        go func(nameIdx int) {
            for tryIdx := 0; tryIdx < tryCnt; tryIdx++ {
                if useCAS {
                    obj.writeMsgWithCASCheck(nameIdx, nil)
                } else {
                    obj.writeMsg(nameIdx, nil)
                }
            }
        }(idx)
    }

    // fmt.Println(casObj.readLoop())
    fmt.Println(obj.readLoop())
    fmt.Println("quit.")
}

Recommended Today

Python series crawlers monitor bitcoin price trend

preface Use Python to monitor and save bitcoin price information. development tool Python version: 3.6.4 Related modules: Requests module; Matplotlib module; Openpyxl module; Numpy module; And some modules that come with Python. Environment construction Install Python and add it to the environment variable. PIP can install the required modules. Main ideas Google can create an […]