Talking about how to use Golang Sync package

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

More Great articles: https://deepzz.com

How to use the Desc:go Sync package, sync. Mutex,sync. Rmutex,sync. Once,sync. Cond,sync. Waitgroup
Although Golang recommends communication and synchronization through the channel, the Sync pack is also very much in real-world development. There is also a atomic package under Sync, which provides some of the underlying atomic operations (not described here). This article mainly introduces some concepts of the lock under the package and how to use it.

The entire package is carried around this Locker, which is a interface:

type Locker interface {        Lock()        Unlock()}

There are only two methods, Lock() and Unlock() .

In addition, the object under the package, after use, do not copy.

There are many students do not understand the concept of lock, the following will be introduced to:

Why do I need a lock?

In the case of concurrency , when multiple threads or processes are modifying a variable at the same time, the following conditions may occur:

package mainimport (    "fmt"    "sync"    "time")func main() {    var a = 0    // 启动 100 个协程,需要足够大    // var lock sync.Mutex    for i := 0; i < 100; i++ {        go func(idx int) {            // lock.Lock()            // defer lock.Unlock()            a += 1            fmt.Printf("goroutine %d, a=%d\n", idx, a)        }(i)    }    // 等待 1s 结束主程序    // 确保所有协程执行完    time.Sleep(time.Second)}

Look at the printout, whether the value of a is the same (no retries or a large number of threads are present), answer: Yes.

Obviously this is not the result we want. The reason for this is that the coprocessor executes sequentially: reads the value of a from the register, and then does the addition operation, the last write register. Imagine that at this point a process takes out the value of a of 3 and is doing an addition operation (not yet written back to the register). At the same time the other process is fetched, the same value of a is removed 3. The result is that the results of both outputs are the same, and a equals only 1 increase.

So, the concept of lock is, I am dealing with a (lock), you do not rob me, and so I finished (unlocked), you deal with. This is achieved, while processing A's process only one, the synchronization is realized.

Cancel the comments in the above code and try again.

What is a mutex mutex?

What is a mutex? It is a specific implementation of the lock, there are two methods:

func (m *Mutex) Lock()func (m *Mutex) Unlock()

Do not copy the mutex after first use. Unlocking an unlocked mutex will result in a run-time error.

A mutex can only be locked by one goroutine at a time, and the other goroutine will block until the mutex is unlocked (re-vying for the lock on the mutex). Such as:

package mainimport (    "fmt"    "sync"    "time")func main() {    ch := make(chan struct{}, 2)    var l sync.Mutex    go func() {        l.Lock()        defer l.Unlock()        fmt.Println("goroutine1: 我会锁定大概 2s")        time.Sleep(time.Second * 2)        fmt.Println("goroutine1: 我解锁了,你们去抢吧")        ch <- struct{}{}    }()    go func() {        fmt.Println("groutine2: 等待解锁")        l.Lock()        defer l.Unlock()        fmt.Println("goroutine2: 哈哈,我锁定了")        ch <- struct{}{}    }()    // 等待 goroutine 执行结束    for i := 0; i < 2; i++ {        <-ch    }}

Note that the usual locking is to lock the mutex, not to say to lock a piece of code. That is, when the code executes to a locked place, it acquires a lock that does not have a mutex, and it blocks there, thus achieving the purpose of controlling synchronization.

What is a read-write lock Rwmutex?

So what is a read-write lock? It is a mutual exclusion lock for read and write operations, and the biggest difference between read-write and mutual-exclusion locks is that they can be locked separately. Generally used in a large number of read operations, a small number of write operations:

func (rw *RWMutex) Lock()func (rw *RWMutex) Unlock()func (rw *RWMutex) RLock()func (rw *RWMutex) RUnlock()

Since there is a need to differentiate between read and write locks, we define:

    • Read lock (Rlock) to lock the read operation
    • Read Unlock (runlock), unlock read lock
    • Write lock (lock), which locks the write operation
    • Write Unlock (Unlock) to unlock the write lock

Do not copy the read-write lock after the first use. Do not mix locking and unlocking, such as: Lock and Runlock, Rlock, and Unlock. A run-time error is caused by a read or write lock on an unread lock, or a written lock on a read-write lock that is not written.

How do you understand read-write locks?

    1. Only one goroutine can get a write lock at the same time.
    2. You can also have any number of Gorouinte to obtain a read lock.
    3. Only write locks or read locks (read and write mutexes) can exist at the same time.

That is, when a goroutine gets a write lock, the rest of the read lock or write lock will block until the write is unlocked; when there is a goroutine read lock, other read locks can continue; when there is one or any number of read locks, the write lock waits for all The write lock is not available until the read lock has been unlocked. So here's the read lock (rlock) purpose is actually to tell the write lock: There are a lot of people are reading the data, you give me stand aside, wait for them to read (read unlock) After you write (write lock).

Examples of Use:

package mainimport (    "fmt"    "math/rand"    "sync")var count intvar rw sync.RWMutexfunc main() {    ch := make(chan struct{}, 10)    for i := 0; i < 5; i++ {        go read(i, ch)    }    for i := 0; i < 5; i++ {        go write(i, ch)    }    for i := 0; i < 10; i++ {        <-ch    }}func read(n int, ch chan struct{}) {    rw.RLock()    fmt.Printf("goroutine %d 进入读操作...\n", n)    v := count    fmt.Printf("goroutine %d 读取结束,值为:%d\n", n, v)    rw.RUnlock()    ch <- struct{}{}}func write(n int, ch chan struct{}) {    rw.Lock()    fmt.Printf("goroutine %d 进入写操作...\n", n)    v := rand.Intn(1000)    count = v    fmt.Printf("goroutine %d 写入结束,新值为:%d\n", n, v)    rw.Unlock()    ch <- struct{}{}}

Waitgroup Example

Waitgroup is used to wait for a set of goroutine to end, and the usage is simple. It has three methods:

func (wg *WaitGroup) Add(delta int)func (wg *WaitGroup) Done()func (wg *WaitGroup) Wait()

Add the number of Goroutine to be added. Done executes a quantity minus 1. Wait waits for the end:

package mainimport (    "fmt"    "sync")func main() {    var wg sync.WaitGroup    for i, s := range seconds {        // 计数加 1        wg.Add(1)        go func(i, s int) {            // 计数减 1            defer wg.Done()            fmt.Printf("goroutine%d 结束\n", i)        }(i, s)    }        // 等待执行结束    wg.Wait()    fmt.Println("所有 goroutine 执行结束")}

Note that the wg.Add() method must be executed before Goroutine starts.

Cond Condition variables

Cond implements a conditional variable, which is a rendezvous point for the goroutines that waits or declares an event to occur.

type Cond struct {    noCopy noCopy      // L is held while observing or changing the condition    L Locker      notify  notifyList    checker copyChecker}

It will save a list of announcements.

func NewCond(l Locker) *Condfunc (c *Cond) Broadcast()func (c *Cond) Signal()func (c *Cond) Wait()

The Wait method, the Signal method, and the broadcast method. They represent actions that wait for notifications, single-shot notifications, and broadcast notifications, respectively.

Let's take a look at the wait method:

func (c *Cond) Wait() {    c.checker.check()    t := runtime_notifyListAdd(&c.notify)    c.L.Unlock()    runtime_notifyListWait(&c.notify, t)    c.L.Lock()}

Its action is: join to the notification list, unlock L--Wait for notification, lock L. It is used in the following ways:

c.L.Lock()for !condition() {    c.Wait()}... make use of condition ...c.L.Unlock()

As an example:

Package main provides ... package Mainimport ("FMT" "Sync" "Time") var count int = 4func main () {ch: = Make (Chan struct{}, 5)//new cond var l sync. Mutex cond: = Sync. Newcond (&l) for I: = 0; I < 5; i++ {go func (i int) {//Scramble for the lock cond of the mutex. L.lock () defer func () {cond.                L.unlock () ch <-struct{}{}} ()//condition reached for count > I { Cond. Wait () fmt. Printf ("received a notification goroutine%d\n", I)} fmt. Printf ("goroutine%d execution ends \ n", i)} (i)}//ensures that all goroutine start complete time. Sleep (Time.millisecond * 20)//Lock it up, I want to change the value of Count FMT. PRINTLN ("Broadcast ...") cond. L.lock () Count-= 1 cond. Broadcast () cond. L.unlock () time. Sleep (time. Second) fmt. PRINTLN ("Signal ...") cond. L.lock () Count-= 2 cond. Signal () cond. L.unlock () time. Sleep (time. Second) fmt. PRINTLN ("Broadcast... ") cond. L.lock () Count-= 1 cond. Broadcast () cond. L.unlock () for I: = 0; I < 5; i++ {<-ch}}

Pool Temporary Object pools

sync.PoolA collection that can be saved and reused as a temporary object. Its structure is:

type Pool struct {    noCopy noCopy    local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal    localSize uintptr        // size of the local array    // New optionally specifies a function to generate    // a value when Get would otherwise return nil.    // It may not be changed concurrently with calls to Get.    New func() interface{}}func (p *Pool) Get() interface{}func (p *Pool) Put(x interface{})

The new key Pool needs to provide an new method to automatically create one (without actively joining the Pool) when the temporary object is not available, and the Get and Put methods are well understood.

In-depth understanding of Go's classmates should know that go's important composition structure is M, P, G. The pool will actually generate a local pool for each P associated with the Goroutine that operates on it. If the local pool is not available from the local pool get object, it is fetched from the other P local pool. Therefore, one of the features of the Pool is that the storage pressure generated by the object values can be apportioned.

It has the following characteristics:

    • Objects in the pool may be automatically deleted if only the pool has a unique index (depending on the time of the next GC execution).
    • Goroutines is safe and can be used by multiple co-processes at the same time.

The execution of the GC generally removes all objects in the Pool.

So what are the scenarios for Pool? From its features, it is applicable to the reuse of stateless objects, and not to those such as connection pooling. There is a good example of using pools in the FMT package, which maintains a dynamic-sized temporary output buffer.

Official examples:

package mainimport (    "bytes"    "io"    "os"    "sync"    "time")var bufPool = sync.Pool{    New: func() interface{} {        return new(bytes.Buffer)    },}func timeNow() time.Time {    return time.Unix(1136214245, 0)}func Log(w io.Writer, key, val string) {    // 获取临时对象,没有的话会自动创建    b := bufPool.Get().(*bytes.Buffer)    b.Reset()    b.WriteString(timeNow().UTC().Format(time.RFC3339))    b.WriteByte(' ')    b.WriteString(key)    b.WriteByte('=')    b.WriteString(val)    w.Write(b.Bytes())    // 将临时对象放回到 Pool 中    bufPool.Put(b)}func main() {    Log(os.Stdout, "path", "/search?q=flowers")}打印结果:2006-01-02T15:04:05Z path=/search?q=flowers

Once Execute Once

Using an object allows the sync.Once function to be called only once for multiple calls. Its structure is:

type Once struct {    m    Mutex    done uint32}func (o *Once) Do(f func())

Use done to record the number of executions, with m to ensure that the guarantee is executed only once. There is only one do method that invokes execution.

package mainimport (    "fmt"    "sync")func main() {    var once sync.Once    onceBody := func() {        fmt.Println("Only once")    }    done := make(chan bool)    for i := 0; i < 10; i++ {        go func() {            once.Do(onceBody)            done <- true        }()    }    for i := 0; i < 10; i++ {        <-done    }}# 打印结果Only once

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.