2021 is also almost over, Go 1.18 features have been frozen and the US is soon in holiday mode, so let’s take this node to review the progress of Go concurrent programming in the last year.
TryLock is finally being released
For a long time (dating back to 2013 #6123), it was proposed to add the TryLock method to Mutex, which was ruthlessly rejected by the big boys, and intermittently, intermittently, it has been proposed that this method is needed, and now in 2021, the Go team bigwigs finally relented and added the corresponding method (#45435).
In a nutshell, Mutex adds TryLock to try to get a lock, and RWMutex adds TryLock and TryRLock methods to try to get a write lock and a read lock. They both return bool type. If they return true, it means that the corresponding lock has been acquired, if they return false, it means that the corresponding lock has not been acquired.
Essentially, it is not too much trouble to implement these methods, so let’s look at the corresponding implementation (without the race code).
First is the Mutex.TryLock:
That is, if the state field is manipulated using aromic.CAS, the lock will be successfully acquired if it is not currently locked or if there are no waiting locks. No attempt will be made to spin and compete with the waiter.
Don’t complain about the style of code above, maybe you think it shouldn’t be written in the following way? The reason is that I removed the race code, those blocks of code contain race code, so it can not be abbreviated as follows.
Read and write locks are a bit tricky, because it has both read and write locks.
First look at RWMutex.TryLock (with the race code removed):
TryLock, try to get the w field lock, if successful, you need to check the current Reader, if there is no reader, then success, if at this point unfortunately there is a reader did not release the read lock, then try Lock is also unsuccessful, return false. note that before returning must be rw.w lock released.
TryRLock(remove the race code):
This code first checks the readerCount, if it is negative, it means there is a writer, so it returns false directly.
If there is no writer, then use atomic.CAS to add 1 to the reader, and if successful, return. If unsuccessful, then there may be other readers joining at this time, or there may be writers joining, because it is not possible to determine whether the reader or writer is joining, then use a for loop and try again.
If the writer joins, then the next cycle c may be a negative number, directly return false, if the reader just joined, then it tries again to add 1 on it.
The above is the new code, not particularly complex. go team reluctantly added these methods, while there are very thoughtful tips (intimidation):
Note that while correct uses of TryLock do exist, they are rare, and use of TryLock is often a sign of a deeper problem in a particular use of mutexes.
Field changes for WaitGroup
Previously, the WaitGroup type used
uint32 as the type of the
state1 field, and in the 64-bit and 32-bit compiler cases, the byte of this field had a different meaning, mainly for alignment purposes. Although the use of a field is very “wise”, but it is very difficult to read, now, Go team changed it to two fields, according to the alignment rules, the 64-bit compiler will align the corresponding field, seriously, we are not short of the 4 bytes.
The meaning of state1 and state2 in the case of 64-bit alignment is clear, but if it is not 64-bit aligned, there is still a clever conversion.
Use fastrandn instead of fastrand in Pool
The Go runtime provides the
fastrandn method, which is much faster than
fastrand() % n, and the related article can be found at the address in the comments below.
So sync.Pool uses
fastrandn to do a little modification to improve performance. Good volume ah, this little performance are to squeeze, the key, this is still the code will be executed only if you turn on the race.
Value adds two convenience methods, Swap and CompareAndSwap
Value, the logic of these two methods is often used if you use sync.Value, which has now been added to the standard library.
Go 1.18 implements generic types, but some library changes are likely to be implemented in future versions. After the introduction of generics, atomic support for types will be greatly enhanced, so it is possible that the type Value will be retired from history and rarely used in the future. (Refer to Russ Cox’s article Updating the Go Memory Model)
Overall, Go’s concurrency-related libraries are relatively stable and have not changed significantly.