Any race is a bug. When there is a race, the compiler is free to do whatever it wants.
https://mp.weixin.qq.com/s/pVJiFdDDKVx707eKL19bjA
谈谈 Golang 中的 Data Race
Any race is a bug
我在接手其他同事的 golang 项目时,一般都会习惯性的做一个竞态检测。有时总会得到一些“惊喜”,比如像下面这段代码:
package main import (
"fmt"
"runtime"
"time"
) var i = 0 func main() {
runtime.GOMAXPROCS(2)
go func() {
for {
fmt.Println("i is ", i)
time.Sleep(time.Second)
}
}()
for {
i += 1
}
}
当通过 go run-race cmd.go
执行时,可以看到有明显的竞态出现:
go run -race g.go
==================
WARNING: DATA RACE
Read at 0x0000006459c0 by goroutine 7:
main.main.func1()
/home/s/goAction/tDataRace/g.go:15 +0x3e Previous write at 0x0000006459c0 by main goroutine:
main.main()
/home/s/goAction/tDataRace/g.go:20 +0x84 Goroutine 7 (running) created at:
main.main()
/home/s/goAction/tDataRace/g.go:13 +0x53
==================
i is 380899
i is 33836429
我觉得不同的 goroutine 并发读写同一个变量,需要加锁,这应该是天经地义的常识。但是总有人以为,不加锁导致的问题最多就是读取的数据是修改前的数据,不能保证原子性罢了。是这样的吗?从上面的输出来看,似乎也差不多,其实这些都是典型的误解。
有些朋友可能不知道,在 Go(甚至是大部分语言)中,一条普通的赋值语句其实并不是一个原子操作(语言规范同样没有定义 i++
是原子操作, 任何变量的赋值都不是原子操作)。例如,在 32 位机器上写 int64
类型的变量是有中间状态的,它会被拆成两次写操作 MOV
—— 写低 32 位和写高 32 位,如下图所示:
如果一个线程刚写完低 32 位,还没来得及写高 32 位时,另一个线程读取了这个变量,那它得到的就是一个毫无逻辑的中间变量,这很有可能使我们的程序出现诡异的 Bug。
而在 Go 的内存模型中,有 race 的 Go 程序的行为是未定义行为,理论上出现什么情况都是正常的。就拿上面的代码来说,当去掉 -race
参数执行时,大概率会得到这样的输出:
i is: 0
i is: 0
i is: 0
i is: 0
而用较老的 go 版本执行时,基本上执行一段时间,程序就会 HANG 住。所以讨论为什么出现这种现象实际上没有任何意义,不要依赖这种行为。
Mutex vs Atomic
解决 race 的问题时,无非就是上锁。可能很多人都听说过一个高逼格的词叫「无锁队列」。 都一听到加锁就觉得很 low,那无锁又是怎么一回事?其实就是利用 atomic
特性,那 atomic
会比 mutex
有什么好处呢?Benign Data Races: What Could Possibly Go Wrong? 的作者总结了这两者的一个区别:
Mutexes do no scale. Atomic loads do.
mutex
由操作系统实现,而 atomic
包中的原子操作则由底层硬件直接提供支持。在 CPU 实现的指令集里,有一些指令被封装进了 atomic
包,这些指令在执行的过程中是不允许中断(interrupt)的,因此原子操作可以在 lock-free 的情况下保证并发安全,并且它的性能也能做到随 CPU 个数的增多而线性扩展。
若实现相同的功能,后者通常会更有效率,并且更能利用计算机多核的优势。所以,以后当我们想并发安全的更新一些变量的时候,我们应该优先选择用 atomic
来实现。
参考资料
The Go Memory Model
Would this race condition be considered a bug?
理解Go标准库中的atomic.Value类型
[s@s tDataRace]$ ll -as
total 2000
4 drwxr-xr-x 2 s go 4096 Dec 21 08:11 .
4 drwxr-xr-x 13 s go 4096 Dec 21 08:03 ..
1988 -rwxr-xr-x 1 s go 2035035 Dec 21 08:11 g
4 -rw-r--r-- 1 s go 200 Dec 21 08:05 g.go
[s@s tDataRace]$ rm -rf g
[s@s tDataRace]$ go build g.go
[s@s tDataRace]$ ll -as
total 2000
4 drwxr-xr-x 2 s go 4096 Dec 21 08:11 .
4 drwxr-xr-x 13 s go 4096 Dec 21 08:03 ..
1988 -rwxr-xr-x 1 s go 2035035 Dec 21 08:11 g
4 -rw-r--r-- 1 s go 200 Dec 21 08:05 g.go
[s@s tDataRace]$ go tool objdump -s main.main g
TEXT main.main(SB) /home/s/goAction/tDataRace/g.go
g.go:11 0x4992e0 64488b0c25f8ffffff MOVQ FS: 0xfffffff8, CX
g.go:11 0x4992e9 483b6110 CMPQ 0x1 0(CX), SP
g.go:11 0x4992ed 7639 JBE 0x49 9328
g.go:11 0x4992ef 4883ec18 SUBQ $0x 18, SP
g.go:11 0x4992f3 48896c2410 MOVQ BP, 0x10(SP)
g.go:11 0x4992f8 488d6c2410 LEAQ 0x1 0(SP), BP
g.go:12 0x4992fd 48c7042402000000 MOVQ $0x 2, 0(SP)
g.go:12 0x499305 e876d3f6ff CALL run time.GOMAXPROCS(SB)
g.go:13 0x49930a c7042400000000 MOVL $0x 0, 0(SP)
g.go:13 0x499311 488d0598b70200 LEAQ 0x2 b798(IP), AX
g.go:13 0x499318 4889442408 MOVQ AX, 0x8(SP)
g.go:13 0x49931d 0f1f00 NOPL 0(A X)
g.go:13 0x499320 e83b3afaff CALL run time.newproc(SB)
g.go:19 0x499325 90 NOPL
g.go:1 0x499326 ebfd JMP 0x49 9325
g.go:11 0x499328 e87386fcff CALL run time.morestack_noctxt(SB)
g.go:11 0x49932d ebb1 JMP main .main(SB) TEXT main.main.func1(SB) /home/s/goAction/tDataRace/g.go
g.go:13 0x499340 64488b0c25f8ffffff MOVQ FS: 0xfffffff8, CX
g.go:13 0x499349 483b6110 CMPQ 0x1 0(CX), SP
g.go:13 0x49934d 0f86a4000000 JBE 0x49 93f7
g.go:13 0x499353 4883ec68 SUBQ $0x 68, SP
g.go:13 0x499357 48896c2460 MOVQ BP, 0x60(SP)
g.go:13 0x49935c 488d6c2460 LEAQ 0x6 0(SP), BP
g.go:15 0x499361 488b0598af0e00 MOVQ mai n.i(SB), AX
g.go:15 0x499368 48890424 MOVQ AX, 0(SP)
g.go:15 0x49936c e86f0df7ff CALL run time.convT64(SB)
g.go:15 0x499371 488b442408 MOVQ 0x8 (SP), AX
g.go:15 0x499376 0f57c0 XORPS X0 , X0
g.go:15 0x499379 0f11442440 MOVUPS X 0, 0x40(SP)
g.go:15 0x49937e 0f11442450 MOVUPS X 0, 0x50(SP)
g.go:15 0x499383 488d0d16b50000 LEAQ 0xb 516(IP), CX
g.go:15 0x49938a 48894c2440 MOVQ CX, 0x40(SP)
g.go:15 0x49938f 488d157a170400 LEAQ 0x4 177a(IP), DX
g.go:15 0x499396 4889542448 MOVQ DX, 0x48(SP)
g.go:15 0x49939b 488d1d7eae0000 LEAQ 0xa e7e(IP), BX
g.go:15 0x4993a2 48895c2450 MOVQ BX, 0x50(SP)
g.go:15 0x4993a7 4889442458 MOVQ AX, 0x58(SP)
print.go:274 0x4993ac 488b05fdb50b00 MOVQ os. Stdout(SB), AX
print.go:274 0x4993b3 488d35662d0400 LEAQ go. itab.*os.File,io.Writer(SB), SI
print.go:274 0x4993ba 48893424 MOVQ SI, 0(SP)
print.go:274 0x4993be 4889442408 MOVQ AX, 0x8(SP)
print.go:274 0x4993c3 488d442440 LEAQ 0x4 0(SP), AX
print.go:274 0x4993c8 4889442410 MOVQ AX, 0x10(SP)
print.go:274 0x4993cd 48c744241802000000 MOVQ $0x 2, 0x18(SP)
print.go:274 0x4993d6 48c744242002000000 MOVQ $0x 2, 0x20(SP)
print.go:274 0x4993df 90 NOPL
print.go:274 0x4993e0 e85b9affff CALL fmt .Fprintln(SB)
g.go:16 0x4993e5 48c7042400ca9a3b MOVQ $0x 3b9aca00, 0(SP)
g.go:16 0x4993ed e86e72fcff CALL tim e.Sleep(SB)
g.go:15 0x4993f2 e96affffff JMP 0x49 9361
g.go:13 0x4993f7 e8a485fcff CALL run time.morestack_noctxt(SB)
g.go:13 0x4993fc 0f1f4000 NOPL 0(A X)
g.go:13 0x499400 e93bffffff JMP main .main.func1(SB)
[s@s tDataRace]$ go build -race g.go
[s@s tDataRace]$ go tool objdump -s main.main g
TEXT main.main(SB) /home/s/goAction/tDataRace/g.go
g.go:11 0x512ba0 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX
g.go:11 0x512ba9 483b6110 CMPQ 0x10(CX), SP
g.go:11 0x512bad 0f8683000000 JBE 0x512c36
g.go:11 0x512bb3 4883ec20 SUBQ $0x20, SP
g.go:11 0x512bb7 48896c2418 MOVQ BP, 0x18(SP)
g.go:11 0x512bbc 488d6c2418 LEAQ 0x18(SP), BP
g.go:11 0x512bc1 488b442420 MOVQ 0x20(SP), AX
g.go:11 0x512bc6 48890424 MOVQ AX, 0(SP)
g.go:11 0x512bca e8315cf8ff CALL runtime.racefuncenter(SB)
g.go:12 0x512bcf 48c7042402000000 MOVQ $0x2, 0(SP)
g.go:12 0x512bd7 e8e454f2ff CALL runtime.GOMAXPROCS(SB)
g.go:13 0x512bdc c7042400000000 MOVL $0x0, 0(SP)
g.go:13 0x512be3 488d050e310300 LEAQ 0x3310e(IP), AX
g.go:13 0x512bea 4889442408 MOVQ AX, 0x8(SP)
g.go:13 0x512bef e88cc2f5ff CALL runtime.newproc(SB)
g.go:20 0x512bf4 488d05c52d1300 LEAQ main.i(SB), AX
g.go:20 0x512bfb 48890424 MOVQ AX, 0(SP)
g.go:20 0x512bff 90 NOPL
g.go:20 0x512c00 e8db5af8ff CALL runtime.raceread(SB)
g.go:20 0x512c05 488b05b42d1300 MOVQ main.i(SB), AX
g.go:20 0x512c0c 4889442410 MOVQ AX, 0x10(SP)
g.go:20 0x512c11 488d0da82d1300 LEAQ main.i(SB), CX
g.go:20 0x512c18 48890c24 MOVQ CX, 0(SP)
g.go:20 0x512c1c 0f1f4000 NOPL 0(AX)
g.go:20 0x512c20 e8fb5af8ff CALL runtime.racewrite(SB)
g.go:20 0x512c25 488b442410 MOVQ 0x10(SP), AX
g.go:20 0x512c2a 48ffc0 INCQ AX
g.go:20 0x512c2d 4889058c2d1300 MOVQ AX, main.i(SB)
g.go:20 0x512c34 ebbe JMP 0x512bf4
g.go:11 0x512c36 e8a526f8ff CALL runtime.morestack_noctxt(SB)
g.go:11 0x512c3b 0f1f440000 NOPL 0(AX)(AX*1)
g.go:11 0x512c40 e95bffffff JMP main.main(SB) TEXT main.main.func1(SB) /home/s/goAction/tDataRace/g.go
g.go:13 0x512c60 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX
g.go:13 0x512c69 483b6110 CMPQ 0x10(CX), SP
g.go:13 0x512c6d 0f86d2000000 JBE 0x512d45
g.go:13 0x512c73 4883ec68 SUBQ $0x68, SP
g.go:13 0x512c77 48896c2460 MOVQ BP, 0x60(SP)
g.go:13 0x512c7c 488d6c2460 LEAQ 0x60(SP), BP
g.go:13 0x512c81 488b442468 MOVQ 0x68(SP), AX
g.go:13 0x512c86 48890424 MOVQ AX, 0(SP)
g.go:13 0x512c8a e8715bf8ff CALL runtime.racefuncenter(SB)
g.go:15 0x512c8f 488d052a2d1300 LEAQ main.i(SB), AX
g.go:15 0x512c96 48890424 MOVQ AX, 0(SP)
g.go:15 0x512c9a e8415af8ff CALL runtime.raceread(SB)
g.go:15 0x512c9f 488b051a2d1300 MOVQ main.i(SB), AX
g.go:15 0x512ca6 48890424 MOVQ AX, 0(SP)
g.go:15 0x512caa e8b18ef2ff CALL runtime.convT64(SB)
g.go:15 0x512caf 488b442408 MOVQ 0x8(SP), AX
g.go:15 0x512cb4 0f57c0 XORPS X0, X0
g.go:15 0x512cb7 0f11442440 MOVUPS X0, 0x40(SP)
g.go:15 0x512cbc 0f11442450 MOVUPS X0, 0x50(SP)
g.go:15 0x512cc1 488d0d58c40000 LEAQ 0xc458(IP), CX
g.go:15 0x512cc8 48894c2440 MOVQ CX, 0x40(SP)
g.go:15 0x512ccd 488d151ce20400 LEAQ 0x4e21c(IP), DX
g.go:15 0x512cd4 4889542448 MOVQ DX, 0x48(SP)
g.go:15 0x512cd9 488d1d40bd0000 LEAQ 0xbd40(IP), BX
g.go:15 0x512ce0 48895c2450 MOVQ BX, 0x50(SP)
g.go:15 0x512ce5 4889442458 MOVQ AX, 0x58(SP)
print.go:274 0x512cea 488d057f331000 LEAQ os.Stdout(SB), AX
print.go:274 0x512cf1 48890424 MOVQ AX, 0(SP)
print.go:274 0x512cf5 e8e659f8ff CALL runtime.raceread(SB)
print.go:274 0x512cfa 488b056f331000 MOVQ os.Stdout(SB), AX
print.go:274 0x512d01 488d0db8fa0400 LEAQ go.itab.*os.File,io.Writer(SB), CX
print.go:274 0x512d08 48890c24 MOVQ CX, 0(SP)
print.go:274 0x512d0c 4889442408 MOVQ AX, 0x8(SP)
print.go:274 0x512d11 488d442440 LEAQ 0x40(SP), AX
print.go:274 0x512d16 4889442410 MOVQ AX, 0x10(SP)
print.go:274 0x512d1b 48c744241802000000 MOVQ $0x2, 0x18(SP)
print.go:274 0x512d24 48c744242002000000 MOVQ $0x2, 0x20(SP)
print.go:274 0x512d2d e88e60ffff CALL fmt.Fprintln(SB)
g.go:16 0x512d32 48c7042400ca9a3b MOVQ $0x3b9aca00, 0(SP)
g.go:16 0x512d3a e8a111f8ff CALL time.Sleep(SB)
g.go:16 0x512d3f 90 NOPL
g.go:15 0x512d40 e94affffff JMP 0x512c8f
g.go:13 0x512d45 e89625f8ff CALL runtime.morestack_noctxt(SB)
g.go:13 0x512d4a e911ffffff JMP main.main.func1(SB)
[s@s tDataRace]$
当通过 go run cmd.go
执行时,大概率会得到下面这样的输出:
i is: 0
i is: 0
i is: 0
i is: 0
然而有些同学提到:之所以输出 0 是因为 i+=1
所在的 goroutine 没有新的栈帧创建,因此没有被调度器调度到。解释似乎也合理,但是事实却不是这样的。真实的原因是:编译器把那段自增的 for
循环给全部优化掉了。
要验证这一点,我们要先从编译器优化说起。传统的编译器通常分为三个部分,前端(frontEnd),优化器(Optimizer)和后端(backEnd)。在编译过程中,前端主要负责词法和语法分析,将源代码转化为抽象语法树;优化器则是在前端的基础上,对得到的中间代码进行优化,使代码更加高效;后端则是将已经优化的中间代码转化为针对各自平台的机器代码。
go 的编译器也一样,在生成目标代码的时候会做很多优化,重要的有:
指令重排
逃逸分析
函数内联
死码消除
查看编译出的二进制可执行文件的汇编代码:
显然,下面这一段直接被优化没了:
for {
i += 1
}
why? 因为这段代码是有竞态的,没有任何同步机制。go 编译器认为这一段是 dead code,索性直接优化掉了。
而当我们通过 go build-race g.go
编译后:
INCQ
指令了,这是因为 -race
选项打开了 data race detector 用来检查这个错误而关闭了相关的编译器优化:此,运行结果就“看似正确”了。
最后再引用一句 golang-nuts 上的评论:
Any race is a bug. When there is a race, the compiler is free to do whatever it wants.
参考资料
Go compiler - Loop transformations
Would this race condition be considered a bug?
Spin-Locks vs. Atomic Instructions - Intel Community https://community.intel.com/t5/Intel-oneAPI-Threading-Building/Spin-Locks-vs-Atomic-Instructions/td-p/894992
c++ - Why is std::mutex faster than std::atomic? - Stack Overflow https://stackoverflow.com/questions/29533755/why-is-stdmutex-faster-than-stdatomic
Benign Data Races: What Could Possibly Go Wrong? https://software.intel.com/content/www/us/en/develop/blogs/benign-data-races-what-could-possibly-go-wrong.html
Comparing the performance of atomic, spinlock and mutex https://demin.ws/blog/english/2012/05/05/atomic-spinlock-mutex/
https://tour.go-zh.org/concurrency/9
sync.Mutex
我们已经看到信道非常适合在各个 Go 程间进行通信。
但是如果我们并不需要通信呢?比如说,若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?
这里涉及的概念叫做 *互斥(mutual*exclusion)* ,我们通常使用 *互斥锁(Mutex)* 这一数据结构来提供这种机制。
Go 标准库中提供了 sync.Mutex
互斥锁类型及其两个方法:
Lock
Unlock
我们可以通过在代码前调用 Lock
方法,在代码后调用 Unlock
方法来保证一段代码的互斥执行。参见 Inc
方法。
我们也可以用 defer
语句来保证互斥锁一定会被解锁。参见 Value
方法。
package main import (
"fmt"
"sync"
"time"
) // SafeCounter 的并发使用是安全的。
type SafeCounter struct {
v map[string]int
mux sync.Mutex
} // Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
c.mux.Unlock()
} // Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
defer c.mux.Unlock()
return c.v[key]
} func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
} time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
// 2021/1/4 20:50 Shawn
package main import (
"fmt"
"time"
) type UnSafeCounter struct {
v map[string]int
} func (c *UnSafeCounter) Inc(key string) {
c.v[key]++
time.Sleep(10 * time.Millisecond)
}
func (c *UnSafeCounter) Value(key string) int {
return c.v[key]
} func main() {
c := UnSafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
go run -race g.go
1000
go run -race g.go
==================
WARNING: DATA RACE
Read at 0x00c000078150 by goroutine 778:
runtime.mapaccess1_faststr()
/home/shawn/go/src/runtime/map_faststr.go:12 +0x0
main.(*UnSafeCounter).Inc()
/home/shawn/gokit/goAction/tMutex2/g.go:14 +0x74
Previous write at 0x00c000078150 by goroutine 1005:
runtime.mapassign_faststr()
/home/shawn/go/src/runtime/map_faststr.go:202 +0x0
main.(*UnSafeCounter).Inc()
/home/shawn/gokit/goAction/tMutex2/g.go:14 +0xc4
Goroutine 778 (running) created at:
main.main()
/home/shawn/gokit/goAction/tMutex2/g.go:24 +0xb9
Goroutine 1005 (running) created at:
main.main()
/home/shawn/gokit/goAction/tMutex2/g.go:24 +0xb9
==================
==================
WARNING: DATA RACE
Read at 0x00c00007a978 by goroutine 778:
main.(*UnSafeCounter).Inc()
/home/shawn/gokit/goAction/tMutex2/g.go:14 +0x87
Previous write at 0x00c00007a978 by goroutine 1005:
main.(*UnSafeCounter).Inc()
/home/shawn/gokit/goAction/tMutex2/g.go:14 +0xd9
Goroutine 778 (running) created at:
main.main()
/home/shawn/gokit/goAction/tMutex2/g.go:24 +0xb9
Goroutine 1005 (running) created at:
main.main()
/home/shawn/gokit/goAction/tMutex2/g.go:24 +0xb9
==================
1000
Found 2 data race(s)
exit status 66
Any race is a bug. When there is a race, the compiler is free to do whatever it wants.的更多相关文章
- 33 Introducing the Go Race Detector
Introducing the Go Race Detector 26 June 2013 Introduction Race conditions are among the most insidi ...
- 竞态条件 race condition data race
竞态条件 race condition Race condition - Wikipedia https://en.wikipedia.org/wiki/Race_condition A race c ...
- 我好像发现了一个Go的Bug?
从一次重构说起 这事儿还得从一次重构优化说起. 最近在重构一个路由功能,由于路由比较复杂,需求变化也多,于是想通过责任链模式来重构,刚好这段时间也在 Sentinel-Go 中看到相关源码. 用责任链 ...
- golang中的race检测
golang中的race检测 由于golang中的go是非常方便的,加上函数又非常容易隐藏go. 所以很多时候,当我们写出一个程序的时候,我们并不知道这个程序在并发情况下会不会出现什么问题. 所以在本 ...
- 28 Data Race Detector 数据种类探测器:数据种类探测器手册
Data Race Detector 数据种类探测器:数据种类探测器手册 Introduction Usage Report Format Options Excluding Tests How To ...
- detect data races The cost of race detection varies by program, but for a typical program, memory usage may increase by 5-10x and execution time by 2-20x.
小结: 1. conflicting access 2.性能危害 优化 The cost of race detection varies by program, but for a typical ...
- 不明显的多线程编程的具体Bugs
我们都知道,在编写多线程程序时,我们应该记住很多细节,比如锁,使用线程安全库等.这里有一个不太明显的bug的列表,特定于多线程程序.其中许多都没有在初学者的文档或教程中提到,但我认为每个使用线程的人最 ...
- C++ 中 ZeroMemory、memset 危险需慎用
使用C/C++编程时,常使用ZeroMemory.memset或 “={0}”来对结构体对象进行初始化或清零.然而这三种方式都有各自的特点,使用时需谨慎,否则容易出现严重错误,本人今日解决一个导致宕机 ...
- ZooKeeper是什么?
What is ZooKeeper? (译:什么是ZooKeeper?) ZooKeeper is a centralized service for maintaining configuratio ...
随机推荐
- SQL Server中datetimeset转换datetime类型问题浅析
在SQL Server中,数据类型datetimeoffset转换为datetime类型或datetime2类型时需要特别注意,有可能一不小心你可能会碰到下面这种情况.下面我们构造一个简单案例,模拟一 ...
- easyui获取table列表中所有数据组装成json格式发送到后台
jsp代码 var rows =$('#findAllRolestable').datagrid('getSelections'); var result = JSON.stringify(rows) ...
- CGLIB(Code Generation Library)详解
什么是CGLIB CGLIB是一个强大的.高性能的代码生成库.其被广泛应用于AOP框架(Spring.dynaop)中,用以提供方法拦截操作.Hibernate作为一个比较受欢迎的ORM框架,同样使用 ...
- [leetcode]236. Lowest Common Ancestor of a Binary Tree树的最小公共祖先
如果一个节点的左右子树上分别有两个节点,那么这棵树是祖先,但是不一定是最小的,但是从下边开始判断,找到后一直返回到上边就是最小的. 如果一个节点的左右子树上只有一个子树上遍历到了节点,那么那个子树可能 ...
- [学习笔记]尝试go-micro开发微服务<第一波>
平时项目都是基于c++,lua,node, 现在打算开始自学开发微服务; 也顺带磨砺下go和docker 前期准备 1. 有golang编程基础 本系列文章是基于有golang编程基础,有过实际开 ...
- linux系统操作系统网卡漂移解决方案及问题原因
一.问题描述 公司有100-150台服务器安装RHEL7.4&中标麒麟7.4系统,为方便编辑配置网卡,使用脚本方式配置为biosname=0,ifname=0,目的是为将en1o2p此类长字符 ...
- Modbus 协议图文详解
1.概论 Modbus是一种串行通信协议,由于其协议简单易用,且没有版权要求,目前已经成为工业领域通信协议的实时标准.ModBus协议是又施耐德电气的前身Modicon公司在1979年提出的.Modb ...
- 软件性能测试分析与调优实践之路-Web中间件的性能分析与调优总结
本文主要阐述软件性能测试中的一些调优思想和技术,节选自作者新书<软件性能测试分析与调优实践之路>部分章节归纳. 在国内互联网公司中,Web中间件用的最多的就是Apache和Nginx这两款 ...
- 紧急预警】关于爆发的 incaseformat 病毒事件亲身体验
相关报道 incaseformat病毒 360安全卫士服务号 https://mp.weixin.qq.com/s/KM6esd1eUlBt-YHtEwnfuw 广东省网络安全应急响应平台 https ...
- win10中安装Linux子系统
前言 Win10的Linux子系统闻名已久,今天就来操作一下 正文 限制 该功能是win10 1809 及之后才加入的功能,故请先核对版本. 开启功能 打开windows设置 设置 -> 更新和 ...