Detalles de interbloqueo de bloqueo

contenido

1. Ejemplo de bloqueo/interbloqueo de canales

En segundo lugar, la solución del punto muerto

1. Método 1: consuma el canal primero

2. Método 2: usar un canal de búfer

3. Secuencia de entrada y salida de los datos del canal

1. Canal sin búfer

2. Canal de búfer

4. Esperando soluciones multi-goroutine


1. Ejemplo de bloqueo/interbloqueo de canales

Interbloqueo: todos los subprocesos o procesos están esperando la liberación de recursos

1) Interbloqueo Ejemplo 1

func main() {
    ch := make(chan int)
    <- ch // 阻塞main goroutine, 信道ch被锁
}

//执行这个程序你会看到Go报这样的错误:
fatal error: all goroutines are asleep - deadlock!

En el programa anterior, solo hay una goroutine, por lo que cuando agrega datos o almacena datos en él, el canal se bloqueará y la goroutine actual se bloqueará

2) Ejemplo de interbloqueo 2

var ch1 chan int = make(chan int)
var ch2 chan int = make(chan int)

func say(s string) {
    fmt.Println(s)
    ch1 <- <- ch2 // ch1 等待 ch2流出的数据
}

func main() {
    go say("hello")
    <- ch1  // 堵塞主线
}

        Entre ellos, la línea principal espera a que fluyan los datos en ch1 y ch1 espera a que fluyan los datos en ch2, pero ch2 espera a que fluyan los datos y ambas rutinas están esperando, es decir, interbloqueo.

3) Resumen de punto muerto

De forma predeterminada, los mensajes de guardar y recuperar del canal se bloquean, es decir, un canal sin búfer suspenderá la rutina actual al buscar y guardar mensajes, a menos que el otro extremo esté listo.

Si no hay flujo de entrada ni de salida, o no hay flujo de entrada ni de salida en un canal sin búfer, se producirá un interbloqueo. O comprenda que los canales sin búfer en todas las rutinas iniciadas por Go deben almacenar datos en una línea y obtener datos en una línea, y deben estar emparejados. Casi todos los datos de acceso a canales no emparejados se interbloquearán, pero también hay algunos especiales, como se indica a continuación:

func main() {
    c := make(chan int)

    go func() {
       c <- 1
    }()
}

        El programa sale normalmente. Es muy simple. No es que nuestro resumen no funcione. Main no esperó a otras gorutinas y terminó de ejecutarse primero, por lo que no fluyó ningún dato al canal c. Se ejecutó un total de una gorutina, y no hubo bloqueo, por lo que no hubo error de interbloqueo. Se dice que permite que el canal bloquee la línea principal ( <- c )

En segundo lugar, la solución del punto muerto

1. Método 1: consuma el canal primero

package main

import (
    "fmt"
)

func f1(in chan int) {
    fmt.Println(<-in)
}

func main() {
    out := make(chan int)
    //调整下位置,先消费
    go f1(out)
    out <- 2
}

2. Método 2: usar un canal de búfer

        Agregar un búfer se convierte en un canal con búfer. El canal con búfer tiene capacidad. Si almacena un dato, puede colocarlo primero en el canal, sin bloquear la línea actual y esperar a que se eliminen los datos. Cuando el canal de búfer alcance el estado completo, mostrará un bloqueo, porque ya no puede transportar más datos en este momento, "debe quitar los datos antes de que pueda fluir en los datos".

El canal con búfer ch puede transmitir 3 elementos sin búfer

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
}

        Si intenta fluir en otros datos, el canal ch bloqueará la línea principal e informará un interbloqueo. Es decir, el canal del búfer se bloqueará cuando esté lleno.

3. Secuencia de entrada y salida de los datos del canal

1. Canal sin búfer

var ch chan int = make(chan int)

func foo(id int) { //id: 这个routine的标号
    ch <- id
}

func main() {
    // 开启5个routine
    for i := 0; i < 5; i++ {
        go foo(i)
    }

    // 取出信道中的数据
    for i := 0; i < 5; i++ {
        fmt.Print(<- ch)
    }
}

Los datos del canal sin búfer son los primeros en llegar, pero el canal sin búfer no almacena datos, solo es responsable del flujo de datos.

2. Canal de búfer

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3

    fmt.Println(<-ch) // 1
    fmt.Println(<-ch) // 2
    fmt.Println(<-ch) // 3
}

El canal de búfer también es el primero en entrar, primero en salir, podemos pensar en el canal de búfer como una cola segura para subprocesos

4. Esperando soluciones multi-goroutine

Bueno, volvamos al problema original, usar el canal para bloquear la línea principal, esperando que se agoten todas las rutinas.

Este es un modelo que abre muchas rutinas pequeñas, cada una de ellas ejecuta la suya propia y finalmente informa a la línea principal cuando terminan.

Discutimos las siguientes 2 versiones del esquema:

  1. Bloquee la línea principal con un solo canal sin búfer
  2. Use un canal almacenado en búfer con una capacidad igual a la cantidad de gorrutinas

Para el escenario 1, el código de ejemplo se vería así:

var quit chan int // 只开一个信道

func foo(id int) {
    fmt.Println(id)
    quit <- 0 // ok, finished
}

func main() {
    count := 1000
    quit = make(chan int) // 无缓冲
    
    for i := 0; i < count; i++ {
        go foo(i)
    }

    for i := 0; i < count; i++ {
        <- quit
    }
}

Para la opción 2, cambie el canal al búfer 1000:

quit = make(chan int, count) // 容量1000

De hecho, la única diferencia es que uno tiene búfer y el otro no.

Para este escenario, ambos pueden realizar la tarea, ambos están bien.

  • Un canal sin búfer es un lote de datos que "entra y sale" uno por uno
  • El canal de búfer se almacena uno por uno y luego se transmiten juntos

Supongo que te gusta

Origin blog.csdn.net/demored/article/details/124147751
Recomendado
Clasificación