原由:

今天早上看到鸟窝的一篇blog Go并发编程小测验: 你能答对几道题? 尝试着做了一下,觉得里面有一些还是比较有意思,所以拿出来分析一下。

1 Mutex

var mu sync.Mutex
var chain string

func main() {
	chain = "main"
	A()
	fmt.Println(chain)
}
func A() {
	mu.Lock()
	defer mu.Unlock()
	chain = chain + " --> A"
	B()
}
func B() {
	chain = chain + " --> B"
	C()
}
func C() {
	// func A里面的lock未释放,此处会报deadlock
	mu.Lock()
	defer mu.Unlock()
	chain = chain + " --> C"
}

2 RWMutex

var mu sync.RWMutex
var count int

func main() {
	go A()
	time.Sleep(2 * time.Second)
	mu.Lock()
	defer mu.Unlock()
	count++
	fmt.Println(count)
}
func A() {
	mu.RLock()
	defer mu.RUnlock()
	B()
}
func B() {
	time.Sleep(5 * time.Second)
	C()
}
func C() {
	// 同理mu.RLock未unlock,再次lock时报deadlock
	mu.RLock()
	defer mu.RUnlock()
}

3 Waitgroup

// panic: sync: WaitGroup is reused before previous Wait has returned
func main() {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		time.Sleep(time.Millisecond)
		wg.Done()
		wg.Add(1)
	}()
	// 前面goroutine的wg没有done,导致此wait发生异常
	wg.Wait()
}

4 双检查实现单例


type Once struct {
	m    sync.Mutex
	done uint32
}

func (o *Once) Do(f func()) {
	// 此处线程不安全,可能其他的goroutine读取到的不是1
	if o.done == 1 {
		return
	}
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		o.done = 1
		f()
	}
}

func main() {
	once := &Once{}
	for i := 0; i < 10; i++ {
		once.Do(func() {
			fmt.Println("aa")
		})
	}
}

5 Mutex

type MyMutex struct {
	count int
	sync.Mutex
}

func main() {
	var mu MyMutex
	mu.Lock()
	var mu2 = mu
	mu.count++
	mu.Unlock()
	// 此时mu2的lock处于lock状态
	// fatal error: all goroutines are asleep - deadlock!
	mu2.Lock()
	mu2.count++
	mu2.Unlock()
	fmt.Println(mu.count, mu2.count)
}

6 Pool

  • TODO 由于对pool和runtime的MemStats不太熟悉,所以先放一下

7 channel

// 休眠1s,保证先执行此(channel 1)先会被创建
// 同时time.Tick返回的也是一个通道
//channel 1
//#goroutines: 2
//channel 2
//
func Channel1() {
	var ch chan int
	go func() {
		ch = make(chan int, 1)
		fmt.Println("channel 1")
		ch <- 1
	}()

	go func(ch chan int) {
		time.Sleep(time.Second)
		fmt.Println("channel 2")
		<-ch
	}(ch)
	c := time.Tick(1 * time.Second)
	for range c {
		fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
	}
}

// 休眠1s,保证先执行此(channel 1)先会被创建,由于创建的是一个没有缓冲的通道,所以ch的值需要被立马消费
// 同时time.Tick返回的也是一个通道
//channel 1
//channel 2
//#goroutines: 3
func Channel2() {
	var ch chan int
	go func() {
		ch = make(chan int)
		fmt.Println("channel 1")
		ch <- 1
	}()

	go func(ch chan int) {
		time.Sleep(time.Second)
		fmt.Println("channel 2")
		<-ch
	}(ch)
	c := time.Tick(1 * time.Second)
	for range c {
		fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine())
	}
}
func main() {
	//Channel1()
	Channel2()
}

8 channel

// panic: close of nil channel
func main() {
	var ch chan int
	var count int
	go func() {
		fmt.Println("channel1")
		ch <- 1
	}()

  // 若注释掉此goroutine,会发生deadlock; <-ch在同一goroutine当中进行操作的缘故
	go func() {
    // ch未被初始化,所以是nil
		count++
		close(ch)
	}()

	<-ch

	fmt.Println(count)
}

// ------------------------------------------

// 输出0
func main() {
	var ch chan int
	var count int
	go func() {
		fmt.Println("channel1")
		ch <- 1
	}()

	go func() {
     // 若把 <-ch放在这里,就可以正常跑下去,因为channel通道在两个协程之间已经通信
		<-ch
		count++
		close(ch)
	}()

	fmt.Println(count)
}

9 Map

并发Map没有长度

10 happens before

由于通道的特性,会阻塞一直等到有数据写入

11 自定义Map

// length并没有添加锁
func (m *Map) Len() int {
	return len(m.m)
}

12 slice

由于两个goroutine都在执行,会有重叠的的部分,所以值的区间是[1000,2000]

13 goroutine

对比一下下面两个test函数,应该就明白为什么一直输出9999999999


func TestGoRoutine(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(10)
	var ts = make([]T, 10)
	for i := 0; i < 10; i++ {
		ts[i] = T{i}
	}
	for _, t := range ts {
		go t.Incr(&wg)
	}
	wg.Wait()
	for _, t := range ts {
		fmt.Println(t)
		go t.Print()
	}
	time.Sleep(5 * time.Second)
}

// 将ts数组里面的变为指针变量
func TestGoRoutine2(t *testing.T) {
	var wg sync.WaitGroup
	wg.Add(10)
	var ts = make([]*T, 10)
	for i := 0; i < 10; i++ {
		ts[i] = &T{i}
	}
	for _, t := range ts {
		go t.Incr(&wg)
	}
	wg.Wait()
	for _, t := range ts {
		fmt.Println(t)
		go t.Print()
	}
	time.Sleep(5 * time.Second)
}

或者修改Print函数也可

func (t *T) Print() {
	time.Sleep(1e9)
	fmt.Print(t.V)
}

func (t T) Print() {
	time.Sleep(1e9)
	fmt.Print(t.V)
}