tag personalized nombre name first etiquetas campaña campaign synchronization go channel goroutine

synchronization - personalized - merge tags first name



¿El bloqueo en un canal envía un mal paradigma de sincronización y por qué? (2)

Effective Go da este ejemplo sobre cómo emular un semáforo con canales:

var sem = make(chan int, MaxOutstanding) func handle(r *Request) { <-sem process(r) sem <- 1 } func init() { for i := 0; i < MaxOutstanding; i++ { sem <- 1 } } func Serve(queue chan *Request) { for { req := <-queue go handle(req) } }

También dice: Debido a que la sincronización de datos ocurre en una recepción de un canal (es decir, el envío "pasa antes" de la recepción; ver Modelo de memoria Go ), la adquisición del semáforo debe realizarse en un canal recibido, no en un envío.

Ahora, creo que entiendo el Modelo de memoria Go y la definición de "pasa antes". Pero no veo cuál es el problema con el bloqueo en un canal de envío:

func handle(r *Request) { sem <- 1 process(r) <-sem } func init() {}

Este código (con sem y Serve sin cambios desde arriba) utiliza el canal de almacenamiento intermedio de la manera opuesta. El canal comienza vacío Al ingresar el handle , el envío se bloqueará si ya hay MaxOutstanding MaxOutstanding haciendo el proceso. Tan pronto como uno de ellos termine su procesamiento y "libere" una ranura del canal, al recibir un int, nuestro envío será desbloqueado y el administrador de rutina comenzará su propio procesamiento.

¿Por qué es esta una mala forma de sincronizar, como parece sugerir el libro de texto?

¿Una operación de recepción que libera una ranura de canal no "sucede antes" del envío que utilizará esa misma ranura? ¿Cómo es esto posible?

En otras palabras, la referencia del lenguaje dice que "un envío en un canal almacenado en el búfer [bloques hasta que] haya espacio en el búfer".

Pero el Modelo de memoria solo dice que "Una recepción de un canal sin búferes ocurre antes de que se complete el envío en ese canal". En particular, no dice que una recepción de un canal almacenado que está lleno sucede antes de que se complete un envío en ese canal.

¿Es este un caso de esquina en el que no se puede confiar para hacer lo correcto? (que en realidad sincronizaría un envío que fue bloqueado con la recepción que lo desbloquea)

Si ese es el caso, parece una desagradable condición de carrera en un lenguaje diseñado para minimizar las condiciones de carrera furtivas :-(

var c = make(chan int, 1) var a string func f() { a = "hello, world" <-c // unblock main, which will hopefully see the updated ''a'' } func main() { c <- 0 // fill up the buffered channel go f() c <- 0 // this blocks because the channel is full print(a) }


Si lo entiendo bien (lo cual es probable que yo no haga), el problema es que el lenguaje no tiene las garantías correctas para qué orden sucederán algunas de estas cosas para que se use de esa manera.

Cuando me he topado con algo así, generalmente he descubierto (a veces después de vergonzosamente mucho ensayo y error) que no era que el lenguaje "faltara algo", sino que estaba tratando de pintar con un martillo.

En el ejemplo específico que tienes arriba, lo resolvería estructurando un poco diferente:

En lugar de tener el semáforo en el emisor (y desbloquearlo en el receptor) solo genere el número deseado de goroutines por adelantado y luego envíeles trabajo a través de un canal. No hay semáforos necesarios. Entiendo que esto fue sólo un ejemplo condensado, pero si describe su caso de uso real / problemas en más detalle, es probable que alguien presente una solución limpia para ello.


Este fragmento del documento Effective Go también me arrojó. De hecho, en versiones relativamente recientes de Effective Go, el código en cuestión adquirió el semáforo en un canal de envío (en lugar de recibir un canal como lo hace en la versión actual, que utiliza el init () para "cebar" el canal).

Aparentemente ha habido una buena cantidad de discusión sobre el tema. No me molestaré en tratar de resumir todo, pero la discusión se puede encontrar desde aquí:

https://code.google.com/p/go/issues/detail?id=5023

Me parece desafortunado, pero citando al archivador de ese problema, la historia corta parece ser que a menos que el semáforo se adquiera en el canal recibir ...:

El siguiente código:

func handle(r *Request) { sem <- 1 // Wait for active queue to drain. process(r) // May take a long time. <-sem // Done; enable next request to run. }

... legalmente podría ser "optimizado" en:

func handle(r *Request) { process(r) // May take a long time. sem <- 1 // Wait for active queue to drain. <-sem // Done; enable next request to run. }

... o en:

func handle(r *Request) { sem <- 1 // Wait for active queue to drain. <-sem // Done; enable next request to run. process(r) // May take a long time. }