Go Exemple de programmation [Mutex]

Instance de base

Dans l’exemple précédent, nous avons vu comment utiliser les opérations atomiques pour gérer un compteur simple.

Pour des situations plus complexes, nous pouvons utiliser un mutex pour accéder en toute sécurité aux données entre les coroutines 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()
}

L'exécution de ce programme montre que nous avons effectué 3 500 000 opérations sur l'état synchronisé.

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

connaissance

Que signifie runtime.Gosched() ?

runtime.Gosched() est une fonction du langage Go. Sa fonction est d'abandonner les droits d'exécution de la goroutine actuelle afin que d'autres goroutines aient la possibilité de s'exécuter.

Dans le langage Go, plusieurs goroutines peuvent s'exécuter simultanément, mais leur temps d'exécution est imprévisible. Lorsqu'une goroutine effectue une longue opération de calcul ou de blocage, les autres goroutines peuvent être suspendues pendant une longue période et ne peuvent pas être exécutées. Afin d'éviter cette situation, nous pouvons activement renoncer aux droits d'exécution de goroutine au moment opportun pour donner à d'autres goroutines une chance de s'exécuter.

La fonction runtime.Gosched() est l'une des fonctions qui atteignent cet objectif.

Lorsque la fonction runtime.Gosched() est appelée, la goroutine actuelle sera suspendue, donnant aux autres goroutines une chance de s'exécuter. Notez que la fonction runtime.Gosched() abandonne uniquement les droits d'exécution de la goroutine actuelle et ne garantit pas que d'autres goroutines seront exécutées. Plus précisément, lorsque la fonction runtime.Gosched() est appelée, le planificateur replanifiera toutes les goroutines exécutables et sélectionnera une goroutine à exécuter selon une certaine stratégie.

Il est à noter que la fonction runtime.Gosched() ne bloquera pas l'exécution de la goroutine actuelle, elle abandonnera seulement les droits d'exécution de la goroutine actuelle. Par conséquent, dans certains cas, nous devons ajouter des opérations d'attente appropriées avant d'appeler la fonction runtime.Gosched() pour nous assurer que la goroutine actuelle a terminé certains travaux.

Par exemple:

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]# 

Dans le code ci-dessus, nous créons deux goroutines, qui génèrent des informations et dorment pendant un certain temps.

Dans chaque goroutine, nous utilisons la fonction runtime.Gosched() pour abandonner les droits d'exécution de la goroutine actuelle afin de garantir que les deux goroutines puissent s'exécuter alternativement. À la fin, nous utilisons la fonction time.Sleep() pour attendre un certain temps afin de nous assurer que les deux goroutines ont une chance de s'exécuter.

Guess you like

Origin blog.csdn.net/weiguang102/article/details/129750346