This is a creation in Article, where the information may have evolved or changed.
Source-level anatomy of the Sync.rwmutex in the Go standard library.
Overview
Rwmutex, read-write lock, also known as "read-write mutex."
A read-write lock is simply a lock that can be used by any number of readers at the same time, or only by a writer.
Read-write locks and mutex ( Mutex
) are similar, but have higher parallelism than mutex, it allows multiple readers to read at the same time, so there are some special application scenarios.
In many scenarios where concurrent programming is possible, data reads more frequently than writes, allowing multiple threads to read a piece of content at the same time.
Case
In go, the value of Rwmutex 0 is an unlocked mutex.
Rwmutex is relatively simple to use, here is a simple example:
package mainimport ("fmt""sync""time")func main() {rw := new(sync.RWMutex)for i := 0; i < 2; i++ { // 建立两个写者go func() {for j := 0; j < 3; j++ {rw.Lock()// 写rw.Unlock()}}()}for i := 0; i < 5; i++ { // 建立两个读者go func() {for j := 0; j < 3; j++ {rw.RLock()// 读rw.RUnlock()}}()}time.Sleep(time.Second)fmt.Println("Done")}
PlayGround
A (magical) excellent (great crater) characteristic
Readers are not able to assume that other readers are able to acquire locks while reading. Therefore, read-lock nesting is forbidden.
Isn't that a little bit around? Here is a "seven second example":?
- First Second: Reader 1 successfully applied for a read lock in the 1th second
- Second second: Writer 1 applies for write lock in 2nd seconds, application failed, blocked, but it will prevent new reader from being locked
- Third second: Reader 2 in the 3rd second request to read the lock, the application failed
- Four seconds: Reader 1 release read lock, writer 1 Get write lock
- Five seconds: Writer 1 release write lock, reader 2 get read lock
- Six seconds: Reader 1 apply again to read the lock, apply successfully, share with reader 2
- Seventh second: Reader 1, reader 2 release read lock, end
When the write lock is blocked, the new read lock cannot be applied, which can effectively prevent the writer from starving. If a thread causes no CPU uptime for some reason, this state is called Hunger .
However, this mechanism also prohibits read lock nesting. Read lock nesting can cause deadlocks:
package mainimport ("fmt""sync""time")func main() {rw := new(sync.RWMutex)var deadLockCase time.Duration = 1go func() {time.Sleep(time.Second * deadLockCase)fmt.Println("Writer Try")rw.Lock()fmt.Println("Writer Fetch")time.Sleep(time.Second * 1)fmt.Println("Writer Release")rw.Unlock()}()fmt.Println("Reader 1 Try")rw.RLock()fmt.Println("Reader 1 Fetch")time.Sleep(time.Second * 2)fmt.Println("Reader 2 Try")rw.RLock()fmt.Println("Reader 2 Fetch")time.Sleep(time.Second * 2)fmt.Println("Reader 1 Release")rw.RUnlock()time.Sleep(time.Second * 1)fmt.Println("Reader 2 Release")rw.RUnlock()time.Sleep(time.Second * 2)fmt.Println("Done")}
Reader 1 and Reader 2 are nested relationships, and according to this schedule, the above program will lead to deadlocks.
And the scary thing about some deadlocks is that it doesn't have to happen. Assume the time in the above program. Sleep is random time, so this piece of code each time the result may be inconsistent, this will give debug a great deal of difficulty.
Guo read lock not nested, write lock nesting long already . (The read lock nesting is also the probability of success, write lock nesting 100% is finished?)
SOURCE Analysis
(Source specific content, the number of lines, in version go version 1.8.1
for example.) )
To make it easy to understand, you can throw away all that you if race.Enabled {...}
don't see. Next, we repeat the "seven second case".?
In the first second, Reader 1 requests a read lock.
Line41: if atomic.AddInt32(&rw.readerCount, 1) < 0 {// A writer is pending, wait for it.runtime_Semacquire(&rw.readerSem)}
The number of readers readerCount
began to be 0, this time add 1, become 1, do not meet the negative conditions so jump out, successfully get read lock a.
The second second, the writer tries to acquire a write lock. The 85th row gets the lock of W. Regardless of whether the read-write lock is successful, it first rejects the other writer.
Line85:// First, resolve competition with other writers.rw.w.Lock()// Announce to readers there is a pending writer.r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders// Wait for active readers.if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {runtime_Semacquire(&rw.writerSem)}
Just now, when a writer is stuck here, he will not let the new reader read it, so it did a very bad thing:
Turn the readercount into a 1-rwmutexmaxreaders.
This will make it possible for the new reader to be stuck.
Next, figure out R equals 1. This means that there are currently written persons present.
Because there are readers, the writer is stuck on the semaphore writerSem
. But it is unwilling to heart ah, thought, "after the current few readers, I will write!" "It silently rw.readerwait the person who is now in possession of the reading lock on the little laptop ." In this example, Readerwait is set to 1.
In the third second, readers 2 try to get read lock, it came to the 41st line, the results found that the number of readers is 1-rwmutexmaxreaders, well, it had to be stuck in the signal volume readerSem
.
In the second, Reader 1 calls Runlock (), which first reduced the number of readers by one, after all, they have not read.
Line61:if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {// A writer is pending.if atomic.AddInt32(&rw.readerWait, -1) == 0 {// The last reader unblocks the writer.runtime_Semrelease(&rw.writerSem)}}
When the number of readers was reduced by one, it found that the number of readers was negative, and this time readers 1 understood that there was a writer waiting to write. It is estimated that reader 1 is already on the readerwait of the writer, so it has reduced readerwait by one, indicating that he is not reading. At this time reader 1 found himself is the last reader, so hurriedly sacrificed writersem, so that the writer can write.
After Reader 1 released the Writersem semaphore, the writer quickly received the reminder, happily acquiring a writing lock and starting his own writing career.
Reader 2 is still stuck ...
Five seconds, the writer 1 wrote a manuscript will not want to write, call unlock () ready to release read lock.
Line114:// Announce to readers there is no active writer.r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)// Unblock blocked readers, if any.for i := 0; i < int(r); i++ {runtime_Semrelease(&rw.readerSem)}
I saw him re-readercount with Rwmutexmaxreaders, so that he became a positive. This positive number happens to be a blocked reader.
Next, the writer releases so many Readersem semaphores according to the number of readers, which is equivalent to waking up one by one of all blocked readers. Reader 2 at the moment of receiving readersem the joy of crying, it can finally read.
Six seconds, the reader 1 again, it adds 1 readers, found it is positive ah, the writer is not now, it once again lucky to get read lock, and Reader 21 read up.
In the seventh second, Reader 1 and Reader 2 release their reading locks. At this point, the end.
noun explanation
English |
English |
explain |
Signal volume (also known as semaphore) |
Semaphore |
|
Condition variable |
Condition |
|
Mutex Amount |
Mutex |
Reference documents
- Wikipedia:semaphore (programming))