题目
创建N个线程(1, 2, 3……N)(线程或协程,这里统一称为线程,下同),要求每个线程按顺序打印一个数字,数字每次加1。当数字达到MAX时,所有的线程正常退出。要求除这N个线程以外,不能有外部线程参与管理。
例如,N=3时,程序的执行顺序为线程1打印1,线程2打印2,线程3打印3,线程1打印4……
思路
这题我被面试面到过,当时没见到过这种并发问题,当时程序写出来了,但是思路也挺混乱的。恰好我又看到了这一篇文章,就想把这个经典问题给终结掉。
这题大体上有两种思路,一种思路是每个线程在运行完成后,顺势通知下一个线程运行,图解如下
| Thread A | Thread B | Thread C |
|---|---|---|
| A.Wait() | B.Wait() | C.Wait() |
| print(number) | print(number2) | print(number3) |
| number ++ | number ++ | number ++ |
| A.Add() | B.Add() | C.Add() |
| B.Done() | C.Done() | A.Done() |
另一种思路是通过多个线程间共享一个变量,然后多个线程争抢锁,并判断是否轮到自己打印数字。图解如下
| Thread A | Thread B | Thread C |
|---|---|---|
| lock() | lock() | lock() |
| A = number | B = number | C = number |
| unlock() | unlock() | unlock() |
| if A % 3 == 0 | if B % 3 == 1 | if C % 3 == 2 |
| print(A);number++ | print(B);number++ | print(C);number++ |
第一种方法没有锁的争抢,第二种方法有锁的争抢。
实现
两种方法都有实现,代码如下
solution1是无锁的,solution2是有锁的
package main
import (
`sync`
)
const (
MAX = 100000 // 打印多少值
GoCount = 4 // 几个协程
)
func main() {
//solution1(MAX, GoCount)
solution2(MAX, GoCount)
}
func solution2(max, goCount int) *[]int{
lock := sync.Mutex{}
wg := sync.WaitGroup{}
result := make([]int, 0, max)
count := 1
wg.Add(goCount)
for i := 0; i < goCount; i++ {
go func(i int) {
for {
lock.Lock()
now := count
lock.Unlock()
if now > max {
wg.Done()
return
}
if now % goCount == i {
//fmt.Println(now)
result = append(result, now)
count ++
}
}
}(i)
}
wg.Wait()
return &result
}
func solution1(max, goCount int) *[]int{
result := make([]int, 0, max)
wgLine := make([]*sync.WaitGroup, goCount, goCount)
wg := &sync.WaitGroup{}
for i := 0; i < goCount; i++ {
wgLine[i] = &sync.WaitGroup{}
wgLine[i].Add(1)
}
count := 1
wg.Add(goCount)
for i := 0; i < goCount; i++ {
go func(max int, selfWg, nextWg *sync.WaitGroup) {
for {
selfWg.Wait()
if count > max {
wg.Done()
selfWg.Add(1)
nextWg.Done()
return
}
//println(count)
result = append(result, count)
count++
selfWg.Add(1)
nextWg.Done()
}
}(max, wgLine[i], wgLine[(i+goCount-1)%goCount])
if i == 0 {
wgLine[goCount-1].Done()
}
}
wg.Wait()
return &result
}
正确性验证
利用go test实现的正确性测试
package main
import (
`testing`
)
const (
TestCount = 10
)
func TestSolution1(t *testing.T) {
for i := 0; i < TestCount; i++ {
result := solution1(MAX, TestCount)
testResult(result, t)
}
}
func TestSolution2(t *testing.T) {
for i := 0; i < TestCount; i++ {
result := solution2(MAX, TestCount)
testResult(result, t)
}
}
func testResult(result *[]int, t *testing.T) {
for i, v := range *result {
if i+1 != v {
t.Error("错了!")
}
}
}
性能测试
性能测试也是基于go test bench
func BenchmarkSolution1(b *testing.B) {
for i := 0; i < b.N; i++ {
solution1(MAX, TestCount)
}
}
func BenchmarkSolution2(b *testing.B) {
for i := 0; i < b.N; i++ {
solution2(MAX, TestCount)
}
}
在我windows10系统上,9代i5 4核,测试出来性能如下(MAX为100000,协程数为4)
PS C:\Project\mygo\src> go test -bench .
goos: windows
goarch: amd64
pkg: mygo
BenchmarkSolution1-8 36 31884292 ns/op
BenchmarkSolution2-8 3 390073667 ns/op
\