Concurrency safety is the most basic common sense, but also the most easily ignored premise, more test an engineer language fundamentals and code specification.

Concurrent access to modify variables can lead to a variety of unpredictable results, the most serious is the program panic, such as the common go language map concurrent read/write panic.

Let’s start with a few examples, the old cliché case, and how to avoid it.

String modification

The following is an example of a concurrent read/write string.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main
import (
    "fmt"
    "time"
)
const (
    FIRST  = "WHAT THE"
    SECOND = "F*CK"
)
func main() {
    var s string
    go func() {
        i := 1
        for {
            i = 1 - i
            if i == 0 {
                s = FIRST
            } else {
                s = SECOND
            }
            time.Sleep(10)
        }
    }()
    for {
        if s == "WHAT" {
            panic(s)
        }
        fmt.Println(s)
        time.Sleep(10)
    }
}

A goroutine repeatedly assigns the variable s, while another main reads the variable s. If it finds that the string reads “WHAT”, it initiates a panic.

1
2
3
4
5
6
7
8
WHAT THE
WHAT THE
panic: WHAT

goroutine 1 [running]:
main.main()
    /Users/zerun.dong/code/gotest/string.go:26 +0x11a
exit status 2

After running the above code, it is destined to panic, the subjective intention of the code is that string assignment is atomic, either F*CK , or WHAT THE , why would WHAT appear?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type StringHeader struct {
    Data uintptr
    Len  int
}

In go, strings are represented by the structure StringHeader, which is clearly written in the source code as non-concurrent safe, so if there happens to be another goroutine that only changes the uintptr but not the Len when reading the string, then the above problem will occur.

Interfaces

Let’s take another example of the error interface, from our POI team. Without the context, it is essentially a panic caused by concurrent changes to the error variable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main
import (
   "fmt"
   "github.com/myteksi/hystrix-go/hystrix"
   "time"
)
var FIRST error = hystrix.CircuitError{Message:"timeout"}
var SECOND error = nil
func main() {
   var err error
   go func() {
      i := 1
      for {
         i = 1 - i
         if i == 0 {
            err = FIRST
         } else {
            err = SECOND
         }
         time.Sleep(10)
      }
   }()
   for {
      if err != nil {
         fmt.Println(err.Error())
      }
      time.Sleep(10)
   }
}

The recurrence case is actually the same.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ITCN000312-MAC:gotest zerun.dong$ go run panic.go
hystrix: timeout
panic: value method github.com/myteksi/hystrix-go/hystrix.CircuitError.Error called using nil *CircuitError pointer

goroutine 1 [running]:
github.com/myteksi/hystrix-go/hystrix.(*CircuitError).Error(0x0, 0xc0000f4008, 0xc000088f40)
    <autogenerated>:1 +0x86
main.main()
    /Users/zerun.dong/code/gotest/panic.go:25 +0x82
exit status 2

Take a look at the definition of an interface in the go language.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 没有方法的interface
type eface struct {
    _type *_type
    data  unsafe.Pointer
}
// 有方法的interface
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

The reasoning is exactly the same, as long as there are concurrent reads and writes, there will be a so-called partial write.

Look at rust

1
2
3
4
5
6
7
fn main() {
    let a = String::from("abc");
    let b = a;

    println!("{}", b);
    println!("{}", a);
}

This is a piece of rust entry-level code that will run with the following error.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ITCN000312-MAC:hello zerun.dong$ cargo run
   Compiling hello v0.1.0 (/Users/zerun.dong/projects/hello)
error[E0382]: borrow of moved value: `a`
 --> src/main.rs:6:20
  |
2 |     let a = String::from("abc");
  |         - move occurs because `a` has type `String`, which does not implement the `Copy` trait
3 |     let b = a;
  |             - value moved here
...
6 |     println!("{}", a);
  |                    ^ value borrowed here after move

error: aborting due to previous error

Because the variable a has been moved, the program can no longer use the variable. This is the concept of rust ownership. The above mentioned problem is avoided at the compiler level, but the rust learning curve is too steep.

How to ensure security

There are many levels to talk about this

language

In simple terms, one big lock is enough, one is not enough, there are 100 segmented locks … for example, statsd agent, because a single agent has a big lock, create more agents on the line, synchronous can not, then switch to asynchronous …

A lot of code is not strict enough to a lock to seriously reduce the performance of the program, in order to program correctly, do not optimize prematurely. Especially business code, performance can not asg expand the heap of machines.

CI/CD

rely on the tool’s linter hint can do some display of the check, including the irregular code or something, are available. But after all, it is not rust compiler check, in fact, the compiler is not omnipotent.

engineer

In the old days, c/c++ programmers knew for every line of code they wrote how variables passed in and out were constructed and destructured, otherwise they wouldn’t even know about memory leaks.

Now the more advanced language, comes with gc brings development efficiency, but does not mean that engineers can not think. If that’s the case, isn’t it true that one day AI will be able to replace programmers, as if yes…

Maybe this is the impossible triangle of high-level languages, development efficiency, mind burden, runtime safety.