This is a creation in Article, where the information may have evolved or changed.
Preview Catalog
- Why do I need a lock?
- What is a mutex mutex?
- What is a read-write lock Rwmutex?
- Waitgroup Example
- Cond Condition variables
- Pool Temporary Object pools
- Once Execute Once
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()andUnlock().
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 main
import (
"fmt"
"sync"
"time"
)
func main () {
var a = 0
// start 100 coroutines, need to be big enough
// 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)
}
// wait 1s to end the main program
// make sure all coroutines are executed
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 main
import (
"fmt"
"sync"
"time"
)
func main () {
ch: = make (chan struct (), 2)
var l sync.Mutex
go func () {
l.Lock ()
defer l.Unlock ()
fmt.Println ("goroutine1: I will lock for about 2s")
time.Sleep (time.Second * 2)
fmt.Println ("goroutine1: I unlocked, you can grab it")
ch <-struct {} {}
} ()
go func () {
fmt.Println ("groutine2: waiting for unlock")
l.Lock ()
defer l.Unlock ()
fmt.Println ("goroutine2: haha, I'm locked")
ch <-struct {} {}
} ()
// wait for goroutine to finish
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?
- Only one goroutine can get a write lock at the same time.
- You can also have any number of Gorouinte to obtain a read lock.
- 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 main
import (
"fmt"
"math / rand"
"sync"
)
var count int
var rw sync.RWMutex
func 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 enters read operation ... \ n", n)
v: = count
fmt.Printf ("goroutine% d reading finished, the value is:% d \ n", n, v)
rw.RUnlock ()
ch <-struct {} {}
}
func write (n int, ch chan struct {}) {
rw.Lock ()
fmt.Printf ("goroutine% d entered write operation ... \ n", n)
v: = rand.Intn (1000)
count = v
fmt.Printf ("goroutine% d write finished, new value:% 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 main
import (
"fmt"
"sync"
)
func main () {
var wg sync.WaitGroup
for i, s: = range seconds {
// count up by 1
wg.Add (1)
go func (i, s int) {
// count down by 1
defer wg.Done ()
fmt.Printf ("goroutine% d end \ n", i)
} (i, s)
}
// wait for execution to end
wg.Wait ()
fmt.Println ("All goroutine execution ends")
}
Note that thewg.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) *Cond
func (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 main
import (
"fmt"
"sync"
"time"
)
var count int = 4
func 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) {
// contention for mutex lock
cond.L.Lock ()
defer func () {
cond.L.Unlock ()
ch <-struct {} {}
} ()
// whether the conditions are met
for count> i {
cond.Wait ()
fmt.Printf ("I received a notification goroutine% d \ n", i)
}
fmt.Printf ("goroutine% d execution finished \ n", i)
} (i)
}
// make sure all goroutines are started
time.Sleep (time.Millisecond * 20)
// lock it, 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 main
import (
"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) {
// Get the temporary object, if not, it will be created automatically
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 ())
// Put the temporary object back into the Pool
bufPool.Put (b)
}
func main () {
Log (os.Stdout, "path", "/ search? Q = flowers")
}
Print results:
2006-01-02T15: 04: 05Z path = / search? Q = flowers
Once Execute Once
Using an object allows thesync.Oncefunction 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 main
import (
"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
}
}
# Print results
Only once
This article links: https://deepzz.com/post/golang-sync-package-usage.html, participating in the comments»
--eof--
Posted in 2017-08-19 21:02:00, and added "go, sync" tags.
This site uses the "Signature 4.0 International" Creative sharing agreement, reproduced please specify the author and the original website. More Instructions»