当前位置:网站首页>Go language core 36 lectures (go language practice and application VII) -- learning notes

Go language core 36 lectures (go language practice and application VII) -- learning notes

2022-06-24 01:28:00 Zhengziming

29 | Atomic manipulation ( On )

We discussed mutexes in the first two articles 、 Read write locks and conditional variables based on them , Let's summarize .

Mutex is a very useful synchronization tool , It can ensure that every moment into the critical zone goroutine only one . The read-write lock treats the write operation and read operation of shared resources differently , And eliminate the mutual exclusion between read operations .

Condition variables are mainly used to coordinate those threads that want to access shared resources . When the state of shared resources changes , It can be used to notify threads blocked by mutexes , It can be based on mutual exclusion , It can also be based on read-write locks . Yes, of course , Read / write locks are also mutually exclusive , The former is an extension of the latter .

Through the rational use of mutex , We can make a goroutine When executing code in critical areas , Not by others goroutine Disturb . however , Although it won't be disturbed , But it may still be interrupted (interruption).

Leading content : Atomic execution and atomic operation

We already know , For one Go Procedure ,Go The scheduler in the language runtime system will properly arrange all of them goroutine Operation of . however , At the same time , There can only be a few goroutine Really running , And this number will only match M The same quantity , Not with G Increase with the increase of .

therefore , For the sake of fairness , The scheduler will always change these frequently goroutine. change It means , To make a goroutine From non operating state to operating state , And make the code in one of them CPU At the heart of it .

Replace It means the opposite , namely : Make one goroutine Code in interrupts execution , And let it change from running state to non running state .

There are many opportunities for this interruption , The gap between the execution of any two statements , Even during the execution of a statement .

Even if these statements are within the critical zone . therefore , We said , Although mutex can ensure the serial execution of code in critical area , But there is no guarantee of the atomicity of the code execution (atomicity).

Among many synchronization tools , Only atomic operation can guarantee atomicity (atomic operation)https://baike.baidu.com/item/%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C/1880992?fr=aladdin . Atomic operations are not allowed to be interrupted while they are in progress . In the underlying , This will happen. CPU Provide chip level support , So it's absolutely effective . Even if you have more CPU The core , Or more CPU In our computer system , The guarantee of atomic operation is also unshakable .

This allows atomic manipulation to completely eliminate race conditions , And can absolutely ensure concurrency security . also , It performs much faster than other synchronization tools , Usually several orders of magnitude higher . however , Its disadvantages are also obvious .

More specifically , It is precisely because atomic operations cannot be interrupted , So it needs to be simple enough , And ask for fast .

You can imagine , If atomic operations are delayed , And it won't be interrupted , How much impact will it have on the efficiency of computer executing instructions . therefore , The operating system level only supports atomic operations on binary bits or integers .

Go The atomic operation of language is, of course, based on CPU And the operating system , Therefore, it also provides atomic operation functions for only a few data types . These functions exist in the standard library code package sync/atomic in .

I usually use the following question to explore the candidate's response to sync/atomic Familiarity with the package .

Our question today is :sync/atomic Several atomic operations are provided in the package ? What are the operable data types ?

The typical answer here is :

sync/atomic The atomic operations that the functions in the package can do are : Add (add)、 Compare and exchange (compare and swap, abbreviation CAS)、 load (load)、 Storage (store) And switching (swap).

These functions don't target many data types . however , For each of these types ,sync/atomic Packages will have a set of functions to support . These data types are :int32、int64、uint32、uint64、uintptr, as well as unsafe In bag Pointer. however , in the light of unsafe.Pointer type , The package does not provide a function for atomic addition .

Besides ,sync/atomic The package also provides a file called Value The type of , It can be used to store any type of value .

Problem analysis

The question is simple , Because the answer is clearly in the code package document . But if you haven't even read the documents , That may not answer , At least I can't give a comprehensive answer .

I usually derive a few more questions from this question . Let me explain one by one .

The first derivative problem : We all know , The first parameter value passed into these atomic operation functions should correspond to the operated value . such as ,atomic.AddInt32 First argument to function , The corresponding must be the integer to be increased . But , Why is the type of this parameter not int32 It is *int32 Well ?

The answer is : Because the atomic operation function needs a pointer to the value to be operated on , Not the value itself ; The parameter values passed into the function are copied , Once a value of this basic type is passed into a function , It has nothing to do with the value outside the function .

therefore , The incoming value itself has no meaning .unsafe.Pointer Although the type is a pointer type , But those atomic operators operate on the pointer value , Not the value it points to , So what you need is still a pointer to the pointer value .

As long as the atomic operation function gets the pointer of the operated value , You can locate the memory address where the value is stored . That's the only way , They can pass through the underlying instructions , Accurately manipulate the data on this memory address .

The second derivative problem : Can the function used for atomic addition do atomic subtraction ? such as ,atomic.AddInt32 Can the function be used to reduce the integer value being manipulated ?

The answer is : Of course you can .atomic.AddInt32 The second parameter of the function represents the difference , Its type is int32, It's symbolic . If we want to do atomic subtraction , Then set the difference to a negative integer .

about atomic.AddInt64 The same is true for functions . however , If you want to use atomic.AddUint32 and atomic.AddUint64 Function to do atomic subtraction , You can't be so direct , Because the type of their second parameter is uint32 and uint64, It's all signed , however , It can also be done , Just a little trouble .

for example , If you want to uint32 The manipulated value of type 18 Do atomic subtraction , For example, the difference is -3, So we can first convert this difference into signed int32 Type value , Then convert the type of the value to uint32, To describe it with an expression is uint32(int32(-3)).

But be careful , Writing directly like this will make Go Language compiler error , It will tell you :“ Constant -3 be not in uint32 Within the range that the type can represent ”, let me put it another way , Doing so will overflow the result value of the expression . however , If we put int32(-3) The resulting value of is assigned to the variable delta, And then delta The value of is converted to uint32 Type value , You can bypass the compiler check and get the correct results .

Last , We take this result as atomic.AddUint32 The second parameter value of the function , You can achieve the right uint32 The value of type is used for the purpose of atomic subtraction .

There is a more direct way . We can give... According to the following expression atomic.AddUint32 The second parameter value of the function :

^uint32(-N-1))

Among them N Represents the difference represented by a negative integer . in other words , Let's first subtract the absolute value of the difference 1, And then put the untyped integer constant , Convert to uint32 Type value , Last , Do a bitwise exclusive or operation on this value , You can get the final parameter value .

The principle of doing so is not complicated . Simply speaking , The complement of the result value of this expression , The complement of the value obtained using the previous method is the same , So these two methods are equivalent . We all know , Integers exist in the form of complements in computers , So here , The same complement of the result value means that the expression is equivalent .

package main

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

func main() {

	//  An example of the second derivative problem .
	num := uint32(18)
	fmt.Printf("The number: %d\n", num)
	delta := int32(-3)
	atomic.AddUint32(&num, uint32(delta))
	fmt.Printf("The number: %d\n", num)
	atomic.AddUint32(&num, ^uint32(-(-3)-1))
	fmt.Printf("The number: %d\n", num)

	fmt.Printf("The two's complement of %d: %b\n", delta, uint32(delta)) // -3 Complement .
	fmt.Printf("The equivalent: %b\n", ^uint32(-(-3)-1)) //  And -3 The complement of is the same .
	fmt.Println()
}

summary

today , We studied together sync/atomic Atomic operation functions and atomic value types provided in the code package . Atomic operation functions are very simple to use , But there are some details that we need to pay attention to . I explained them one by one in the derivative problems of the main problem .

In the next article , We will continue to share derivatives of atomic operations .

Note source code

https://github.com/MingsonZheng/go-core-demo

原网站

版权声明
本文为[Zhengziming]所创,转载请带上原文链接,感谢
https://yzsam.com/2021/11/20211118085336397a.html