Ir al ejemplo de programación [Mutexes]

Instancia básica

En el ejemplo anterior, vimos cómo utilizar operaciones atómicas para gestionar un contador simple.

Para situaciones más complejas, podemos usar un mutex para acceder de forma segura a los datos entre rutinas de Go.

// mutexes.go
package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"sync"
	"sync/atomic"
	"time"
)

func main() {
    
    

	// 在我们的例子中,`state` 是一个 map。
	var state = make(map[int]int)

	// 这里的 `mutex` 将同步对 `state` 的访问。
	var mutex = &sync.Mutex{
    
    }

	// 为了比较基于互斥锁的处理方式和我们后面将要看到的其他
	// 方式,`ops` 将记录我们对 state 的操作次数。
	var ops int64 = 0

	// 这里我们运行 100 个 Go 协程来重复读取 state。
	for r := 0; r < 100; r++ {
    
    
		go func() {
    
    
			total := 0
			for {
    
    

				// 每次循环读取,我们使用一个键来进行访问,
				// `Lock()` 这个 `mutex` 来确保对 `state` 的
				// 独占访问,读取选定的键的值,`Unlock()` 这个
				// mutex,并且 `ops` 值加 1。
				key := rand.Intn(5)
				mutex.Lock()
				total += state[key]
				mutex.Unlock()
				atomic.AddInt64(&ops, 1)

				// 为了确保这个 Go 协程不会在调度中饿死,我们
				// 在每次操作后明确的使用 `runtime.Gosched()`
				// 进行释放。这个释放一般是自动处理的,像例如
				// 每个通道操作后或者 `time.Sleep` 的阻塞调用后
				// 相似,但是在这个例子中我们需要手动的处理。
				runtime.Gosched()
			}
		}()
	}

	// 同样的,我们运行 10 个 Go 协程来模拟写入操作,使用
	// 和读取相同的模式。
	for w := 0; w < 10; w++ {
    
    
		go func() {
    
    
			for {
    
    
				key := rand.Intn(5)
				val := rand.Intn(100)
				mutex.Lock()
				state[key] = val
				mutex.Unlock()
				atomic.AddInt64(&ops, 1)
				runtime.Gosched()
			}
		}()
	}

	// 让这 10 个 Go 协程对 `state` 和 `mutex` 的操作
	// 运行 1 s。
	time.Sleep(time.Second)

	// 获取并输出最终的操作计数。
	opsFinal := atomic.LoadInt64(&ops)
	fmt.Println("ops:", opsFinal)

	// 对 `state` 使用一个最终的锁,显示它是如何结束的。
	mutex.Lock()
	fmt.Println("state:", state)
	mutex.Unlock()
}

La ejecución de este programa muestra que realizamos 3.500.000 operaciones en el estado sincronizado.

[root@bogon test]# go run mutexes.go
ops: 3789768
state: map[0:36 1:58 2:93 3:56 4:37]
[root@bogon test]# 

conocimiento

¿Qué significa runtime.Gosched()?

runtime.Gosched () es una función en el lenguaje Go. Su función es ceder los derechos de ejecución de la gorutina actual para que otras gorutinas tengan la oportunidad de ejecutarse.

En el lenguaje Go, se pueden ejecutar varias gorutinas al mismo tiempo, pero su tiempo de ejecución es impredecible. Cuando una gorutina realiza un cálculo prolongado o una operación de bloqueo, otras gorutinas pueden suspenderse durante mucho tiempo y no pueden ejecutarse. Para evitar esta situación, podemos ceder activamente los derechos de ejecución de goroutines en el momento adecuado para dar a otras goroutines la oportunidad de ejecutarse.

La función runtime.Gosched() es una de las funciones que logra este propósito.

Cuando se llama a la función runtime.Gosched(), la rutina actual se suspenderá, dando a otras rutinas la oportunidad de ejecutarse. Tenga en cuenta que la función runtime.Gosched() solo cede los derechos de ejecución de la rutina actual y no garantiza que se ejecutarán otras rutinas. Específicamente, cuando se llama a la función runtime.Gosched(), el programador reprogramará todas las gorutinas ejecutables y seleccionará una gorutina para su ejecución de acuerdo con una determinada estrategia.

Cabe señalar que la función runtime.Gosched() no bloqueará la ejecución de la gorutina actual, solo cederá los derechos de ejecución de la gorutina actual. Por lo tanto, en algunos casos, necesitamos agregar algunas operaciones de espera apropiadas antes de llamar a la función runtime.Gosched() para asegurarnos de que la rutina actual haya completado cierto trabajo.

Por ejemplo:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
    
    
	go func() {
    
    
		for i := 0; i < 10; i++ {
    
    
			fmt.Println("goroutine 1:", i)
			time.Sleep(time.Millisecond * 100)
			// 让出当前 goroutine 的执行权
			runtime.Gosched()
		}
	}()
	go func() {
    
    
		for i := 0; i < 10; i++ {
    
    
			fmt.Println("goroutine 2:", i)
			time.Sleep(time.Millisecond * 100)
			// 让出当前 goroutine 的执行权
			runtime.Gosched()
		}
	}()
	// 等待一段时间,确保两个 goroutine 都能有机会运行
	time.Sleep(time.Second)
}

[root@bogon test]# go run main.go 
goroutine 2: 0
goroutine 1: 0
goroutine 1: 1
goroutine 2: 1
goroutine 2: 2
goroutine 1: 2
goroutine 1: 3
goroutine 2: 3
goroutine 2: 4
goroutine 1: 4
goroutine 1: 5
goroutine 2: 5
goroutine 2: 6
goroutine 1: 6
goroutine 1: 7
goroutine 2: 7
goroutine 2: 8
goroutine 1: 8
goroutine 1: 9
goroutine 2: 9
[root@bogon test]# 

En el código anterior, creamos dos gorutinas, que generan cierta información y duermen durante un período de tiempo.

En cada gorutina, utilizamos la función runtime.Gosched() para ceder los derechos de ejecución de la gorutina actual para garantizar que las dos gorutinas puedan ejecutarse alternativamente. Al final, usamos la función time.Sleep() para esperar un tiempo y asegurarnos de que ambas rutinas tengan la oportunidad de ejecutarse.

Supongo que te gusta

Origin blog.csdn.net/weiguang102/article/details/129750346
Recomendado
Clasificación