Ir detector de carrera

documentación del detector de carreras go: Presentación del detector de carreras go

Las condiciones de carrera son uno de los errores de programación más insidiosos y esquivos. A menudo causan inestabilidad y fallas misteriosas, a menudo mucho después de que el código se haya implementado en producción. Si bien los mecanismos de concurrencia de Go facilitan la escritura de código concurrente limpio, no evitan las condiciones de carrera. Requiere precaución, diligencia y pruebas. Las herramientas pueden ayudar.

El detector de carrera está integrado con la cadena de herramientas go. Cuando se establece el indicador de línea de comandos -race, el compilador detecta todos los accesos a la memoria utilizando un código que registra cuándo y cómo se accede a la memoria, mientras que la biblioteca en tiempo de ejecución monitorea los accesos asincrónicos a las variables compartidas. Cuando se detecta un comportamiento tan "indecente", se imprime una advertencia. (Consulte este artículo para obtener detalles sobre el algoritmo).

Debido a su diseño, el detector de carrera solo puede detectar condiciones de carrera cuando realmente son activadas por el código en ejecución , lo que significa que es importante ejecutar binarios habilitados para carrera bajo cargas de trabajo reales. Sin embargo, un binario habilitado para carreras puede utilizar diez veces más CPU y memoria, por lo que no es práctico habilitar siempre el detector de carreras . Una forma de solucionar este dilema es realizar algunas pruebas con el detector de carrera habilitado. Las pruebas de carga y las pruebas de integración son buenos candidatos porque tienden a ejecutar partes simultáneas del código. Otra forma de utilizar cargas de trabajo de producción es implementar una instancia habilitada para contención en un grupo de servidores en ejecución.

El detector de carreras está completamente integrado con la cadena de herramientas Go. Para compilar su código con el detector de carreras habilitado, simplemente agregue el indicador -race a la línea de comando:

$ go test -race mypkg    // test the package
$ go run -race mysrc.go  // compile and run the program
$ go build -race mycmd   // build the command
$ go install -race mypkg // install the package

Usar detector de contención

Para probar el detector de carreras usted mismo, copie este programa de muestra en race.go:

package main

import "fmt"

func main() {
    
    
    done := make(chan bool)
    m := make(map[string]string)
    m["name"] = "world"
    go func() {
    
    
        m["name"] = "data race"
        done <- true
    }()
    fmt.Println("Hello,", m["name"])
    <-done
}

Luego ejecútelo con el detector de carrera habilitado:

$ go run -race racy.go

Descubrí que hay competencia en el código anterior.

Ejemplos

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    
    
    start := time.Now()
    var t *time.Timer
    t = time.AfterFunc(randomDuration(), func() {
    
    
        fmt.Println(time.Now().Sub(start))
        t.Reset(randomDuration())
    })
    time.Sleep(5 * time.Second)
}

func randomDuration() time.Duration {
    
    
    return time.Duration(rand.Int63n(1e9))
}

Este parece un código razonable, pero en algunos casos falla de manera sorprendente:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x8 pc=0x41e38a]

goroutine 4 [running]:
time.stopTimer(0x8, 0x12fe6b35d9472d96)
    src/pkg/runtime/ztime_linux_amd64.c:35 +0x25
time.(*Timer).Reset(0x0, 0x4e5904f, 0x1)
    src/pkg/time/sleep.go:81 +0x42
main.func·001()
    race.go:14 +0xe3
created by time.goFunc
    src/pkg/time/sleep.go:122 +0x48

¿Que está pasando aqui? Ejecutar el programa con el detector de carrera habilitado es más revelador:

==================
WARNING: DATA RACE
Read by goroutine 5:
  main.func·001()
     race.go:16 +0x169

Previous write by goroutine 1:
  main.main()
      race.go:14 +0x174

Goroutine 5 (running) created at:
  time.goFunc()
      src/pkg/time/sleep.go:122 +0x56
  timerproc()
     src/pkg/runtime/ztime_linux_amd64.c:181 +0x189
==================

El detector de carreras muestra el problema: lecturas y escrituras asincrónicas de la variable t de diferentes gorutinas. Si la duración inicial del temporizador es muy corta, la función del temporizador puede activarse antes de que la rutina principal asigne un valor a t, por lo que la llamada a t.Reset se realiza con nil t.

La opinión personal de Qingfeng:

  • Este mensaje de error lo genera Data Race Detector en el idioma Go. Significa que durante la ejecución del programa, dos gorutinas leyeron y escribieron la misma variable respectivamente, y no hubo operación de sincronización entre ellas, lo que resultó en un problema de carrera de datos.
  • Específicamente, en este mensaje de error, la rutina 5 leyó el valor de una variable en la función main.func·001() y la rutina 1 escribió el valor de la variable en la función main.main(). Dado que estas dos rutinas no realizan operaciones de sincronización, puede haber un problema de desincronización de lectura y escritura, lo que provocará competencia de datos.
  • Para resolver este problema, puede utilizar las primitivas de sincronización proporcionadas por el lenguaje Go (como bloqueos mutex, variables de condición, etc.) para proteger el acceso a las variables compartidas. Específicamente, puede adquirir un bloqueo mutex en el bloque de código que escribe la variable, luego realizar la operación de escritura y finalmente liberar el bloqueo. Es necesario adquirir el mismo mutex en el bloque de código que lee la variable para garantizar que se lea el último valor. Esto evita que varias gorutinas accedan a variables compartidas al mismo tiempo, resolviendo así el problema de la carrera de datos.

Para corregir la condición de carrera, cambiamos el código para leer y escribir solo la variable t de la rutina principal:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    
    
    start := time.Now()
    reset := make(chan bool)
    var t *time.Timer
    t = time.AfterFunc(randomDuration(), func() {
    
    
        fmt.Println(time.Now().Sub(start))
        reset <- true
    })
    for time.Since(start) < 5*time.Second {
    
    
        <-reset
        t.Reset(randomDuration())
    }
}

func randomDuration() time.Duration {
    
    
    return time.Duration(rand.Int63n(1e9))
}
$ go run -race main.go
963.1441ms
1.0586156s
1.7398158s
1.9771881s
2.278302s
2.8400051s
3.4801887s
3.8141354s
4.0054143s
4.4983853s
5.2601257s
5.5267243s

Supongo que te gusta

Origin blog.csdn.net/weixin_37909391/article/details/130890166
Recomendado
Clasificación