우리 모두는 동시 프로그래밍에서 스레드 안전성이 매우 중요하다는 것을 알고 있습니다. 다음으로 스레드가 안전하지 않은 상황을 재현하는 시나리오를 가정하고 Go에서
을 해결하는 방법에 대해 이야기하겠습니다. 이제 1부터 100까지 팩토리얼을 찾아 결과를 a에 입력해야 합니다. map
1! = 1 = 1 2! = 1 * 2 = 2 3! = 1 * 2 * 3 = 6 4! = 1 * 2 * 3 * 4 = 24 5! = 1 * 2 * 3 * 4 * 5 = 120 ... { 1: 1 2: 2 3: 6 4: 24 5: 120 ... }
var factorialMap = make(map[int]int) func Factorial(n int) { result := 1 for i := 1; i <= n; i++ { result *= i } factorialMap[n] = result } func main() { for i := 1; i < 10; i++ { Factorial(i) } for k, v := range factorialMap { fmt.Printf("%d 的阶乘是%d\n", k, v) } }
코드 구현 위의 코드 실행 결과는 실제로 문제가 없습니다. 왜 순서가 뒤바뀌는 상황이 발생하는 걸까요? 이것은 Go 언어의 지도이기 때문에 실제로는 순서가 맞지 않습니다. 우리가 이해한 바에 따르면 선입선출이지만 죄송하지만 Golang의 지도는 이와 같지 않습니다. 위의 실행에는 문제가 없습니다. 주의 깊은 학생들은 이 버전의 코드가 동시성을 사용하지 않는다는 것을 발견했을 것입니다. 자,
var factorialMap = make(map[int]int) func Factorial(n int) { result := 1 for i := 1; i <= n; i++ { result *= i } factorialMap[n] = result } func main() { for i := 1; i < 10; i++ { go Factorial(i) } for k, v := range factorialMap { fmt.Printf("%d 的阶乘是%d\n", k, v) } }
을 계속 개선해 보겠습니다. 동시 버전에서는 계승 함수를 계산하기 위해 호출 앞에 go
而已。不要小看这个go
를 추가하는 것을 볼 수 있는데, 이는 너무 멀리 떨어져 있습니다. 물론 모두가 알고 있는 사실입니다. 이것은 go 언어에서 함수를 여는 것입니다. 코루틴의 키워드를 사용하세요.
실행 결과 콘솔에 아무것도 출력되지 않는 이유는 메인 코루틴과 서브 코루틴의 실행 관계 때문입니다. 이해를 돕기 위해 그림을 그려보겠습니다.
위 그림을 보면 알 수 있습니다. 메인 코루틴의 실행 시간은 짧고(비교적 짧은 것으로 표시), 서브 코루틴의 실행 시간은 상대적으로 길다(비교적 긴 것으로 표시).
서브 코루틴은 현재 메인 코루틴에 상대적이라는 점을 기억해야 합니다. 메인 코루틴이 더 이상 존재하지 않으면 서브 코루틴도 존재하지 않게 됩니다. 따라서 메인 코루틴 프로세스가 실행되었기 때문에 위의 코드는 아무것도 출력하지 않습니다. 그런데 하위 코루틴이 완료되지 않은 경우, factorialMap
에 아무것도 없을 수 있나요?
메인 및 기타 하위 코루틴factorialMap
中能有东西吗?
这就引出我们第一个问题,主协程如何等待子协程执行完再退出程序。我们现在用一个最简单,最容易想到的做法
var factorialMap = make(map[int]int) func Factorial(n int) { result := 1 for i := 1; i <= n; i++ { result *= i } factorialMap[n] = result } func main() { for i := 1; i < 100; i++ { go Factorial(i) } time.Sleep(time.Second * 3) for k, v := range factorialMap { fmt.Printf("%d 的阶乘是%d\n", k, v) } }
当并发数比较小的时候,这个问题可能不会出现,一旦并发数变大,问题就立马出现了
图中的执行结果是并发map写入错误为什么会出现这个问题,我们假设100个人往一个篮子里放水果,很容易。但是100个人从一个篮子里拿水果,那就会出问题,首先,篮子里的水果不一定够100个,其二每个人都想先拿,必然会引起争抢。
针对上面的问题,我们引入全局锁的概念。这就有点像我们上厕所,100个人都想上厕所,但厕所只有1个,谁先抢到了谁先上,并且这个人还有给厕所上锁,防止其他人进来
var factorialMap = make(map[int]int) var lock sync.Mutex func Factorial(n int) { result := 1 for i := 1; i <= n; i++ { result *= i } // defer 不好理解 // defer func(){ // lock.Unlock() // 执行完解锁 // }() lock.Lock() // 执行时上锁 factorialMap[n] = result lock.Unlock() // 执行后解锁 } func main() { for i := 1; i < 100; i++ { go Factorial(i) } time.Sleep(time.Second * 3) for k, v := range factorialMap { fmt.Printf("%d 的阶乘是%d\n", k, v) } }
执行结果有0可能是数据类型存不下了导致的,这个大家不用关心
这样我们就解决了资源竞争的问题了。但其实还有一个问题,就是我们在主协程中还是必须手动等待,这要非常不好,那如果子协程3秒内解决不了怎么办?
这个问题是我们不想在主协程中手动等待子协程,换句话说是我们不想直接在代码中写明要等待多长时间
这里我们就引入了WaitGroup
이것은 프로그램을 종료하기 전에 메인 코루틴이 하위 코루틴의 실행이 완료될 때까지 어떻게 기다리는지에 대한 첫 번째 질문으로 이어집니다. 우리는 이제 가장 간단하고 쉬운 생각 방법을 사용하고 있습니다WaitGroup
var factorialMap = make(map[int]int) var lock sync.Mutex var wg sync.WaitGroup func Factorial(n int) { result := 1 for i := 1; i <= n; i++ { result *= i } lock.Lock() // 执行时上锁 factorialMap[n] = result lock.Unlock() // 执行后解锁 wg.Done() } func main() { for i := 1; i < 100; i++ { wg.Add(1) go Factorial(i) } wg.Wait() for k, v := range factorialMap { fmt.Printf("%d 的阶乘是%d\n", k, v) } }
Concurrent 맵입니다. writing error 왜 이런 문제가 발생할까요? 100명이 과일을 바구니에 담았다고 가정해 보겠습니다. 하지만 100명이 바구니에서 과일을 가져간다면 문제가 생길 것입니다. 첫째, 바구니에 과일이 충분하지 않을 수 있습니다. 둘째, 모두가 먼저 가져가고 싶어하므로 필연적으로 경쟁이 발생합니다.
위 내용은 Go 언어의 자원 경쟁 문제에 대해 이야기하는 기사의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!