MEASUREMENT
bench/goslice/racesrc/main.go
The script that produced the numbers in the article, and the record of the run. The file is read from the repository at build time — this is the code that was run, not a copy of it.
- Cited in
- /en/go/data-structures/slices
The run below is recorded in Russian. It is a lab record, kept in the language it was written in; the numbers, the tables and the code read the same either way.
Record of the run
This measurement has no recorded run — only the script.
Script
159 lines// Подопытная программа для bench/goslice/race_shared.go.
//
// Четыре режима: два ломаных и два починенных. Ломаные существуют ровно для
// того, чтобы детектор гонок было на чём показать, — это материал защитный,
// а не рецепт. Оба лечатся, и лечение здесь же, рядом, в тех же терминах.
//
// Запускать этот файл руками не нужно, его запускает драйвер:
//
// go run bench/goslice/race_shared.go
//
// Вручную, если хочется:
//
// cd bench/goslice && go run -race ./racesrc overlap
package main
import (
"fmt"
"os"
"slices"
"sync"
)
// sinkA и sinkB не дают компилятору выбросить результат append как
// неиспользуемый. Их ДВА, и каждый пишется ровно одной горутиной: один общий
// sink сам стал бы гонкой и увёл бы отчёт детектора не туда.
var (
sinkA []int
sinkB []int
)
const iters = 2000
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "режим: overlap | appendshared | fixedsplit | fixedclip")
os.Exit(2)
}
switch os.Args[1] {
case "overlap":
overlap()
case "appendshared":
appendShared()
case "fixedsplit":
fixedSplit()
case "fixedclip":
fixedClip()
default:
fmt.Fprintln(os.Stderr, "неизвестный режим:", os.Args[1])
os.Exit(2)
}
}
// overlap — ЛОМАНЫЙ случай №1: два среза нарезаны из одного массива с
// перехлёстом. a[2] и b[0] — это один и тот же элемент buf[2].
// Ни один из индексов не выглядит подозрительно; общий у них массив, а не
// индекс.
func overlap() {
buf := make([]int, 8)
a := buf[0:4]
b := buf[2:6] // перехлёст: buf[2] и buf[3] попали в оба среза
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < iters; i++ {
a[2] = i // == buf[2]
}
}()
go func() {
defer wg.Done()
for i := 0; i < iters; i++ {
b[0] = i // == buf[2], тот же адрес
}
}()
wg.Wait()
fmt.Println("overlap: buf =", buf)
}
// appendShared — ЛОМАНЫЙ случай №2: тот же массив, но общий у срезов не
// элемент, а ЗАПАС. Оба среза имеют len=1 и cap=8, поэтому оба append пишут
// в buf[1] — в ячейку, которой ни в одном из срезов ещё нет.
// Это та же ошибка, от которой спасает slices.Clip.
func appendShared() {
buf := make([]int, 1, 8)
a := buf[:1] // len=1 cap=8
b := buf[:1] // len=1 cap=8
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < iters; i++ {
sinkA = append(a, i) // пишет в buf[1]
}
}()
go func() {
defer wg.Done()
for i := 0; i < iters; i++ {
sinkB = append(b, -i) // пишет в тот же buf[1]
}
}()
wg.Wait()
fmt.Println("appendshared: buf[:cap] =", buf[:cap(buf)])
}
// fixedSplit — ЛЕЧЕНИЕ случая №1: непересекающиеся половины.
// Массив по-прежнему один, и это нормально: гонка бывает не от общего
// массива, а от общей ЯЧЕЙКИ. Пока диапазоны не пересекаются, писать в них
// параллельно можно.
func fixedSplit() {
buf := make([]int, 8)
a := buf[0:4]
b := buf[4:8]
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < iters; i++ {
a[2] = i // buf[2]
}
}()
go func() {
defer wg.Done()
for i := 0; i < iters; i++ {
b[0] = i // buf[4] — другая ячейка
}
}()
wg.Wait()
fmt.Println("fixedsplit: buf =", buf)
}
// fixedClip — ЛЕЧЕНИЕ случая №2: отобрать у среза чужой запас.
// slices.Clip(s) — это s[:len(s):len(s)]. После него cap == len, и append
// обязан выделить новый массив, а значит горутины перестают делить ячейку.
func fixedClip() {
buf := make([]int, 1, 8)
a := slices.Clip(buf[:1]) // len=1 cap=1
b := slices.Clip(buf[:1]) // len=1 cap=1
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < iters; i++ {
sinkA = append(a, i) // новый массив на каждый append
}
}()
go func() {
defer wg.Done()
for i := 0; i < iters; i++ {
sinkB = append(b, -i)
}
}()
wg.Wait()
fmt.Println("fixedclip: buf[:cap] =", buf[:cap(buf)], "(запас не тронут)")
}