Go语言并发代码学习实例

本文主要是对 Generic Data Structures and Algorithms in Go 一书中的并发代码的学习。

go key word

go关键词可以新建一个协程运行代码

package main

import (
  "fmt"
  "time"
)

func regularFunction() {
  fmt.Println("Just call regularFunction()")
  time.Sleep(time.Second * 5)
}

func goroutineFunction() {
  fmt.Println("Just called goroutineFunction()")
  time.Sleep(time.Second * 3)
  fmt.Println("goroutineFunction() end")
}

func main() {
  go goroutineFunction()
  fmt.Println("In main 1 line below goroutineFunction()")
  regularFunction()
  fmt.Println("In main 1 line below regularFuncion()")
}

如果主的协程不等待,那么当子的协程运行不完,整个程序也会结束,类似子线程没有join:

package main

import (
	"fmt"
	"time"
)

func goroutineFunction() {
	fmt.Println("Just called goroutineFunction()")
	time.Sleep(time.Second * 3)
	fmt.Println("goroutineFunction() end")
}

func main() {
	go goroutineFunction()
}

WaitGroup

WaitGroup可以用来等待多个子协程结束后,整个程序结束:

package main

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

var wg sync.WaitGroup

func outputStrings() {
	defer wg.Done()
	strings := [5]string{"One", "Two", "Three", "Four", "Five"}
	for i := 0; i < 5; i++ {
		delay := 1 + rand.Intn(3)
		time.Sleep(time.Duration(delay) * time.Second)
		fmt.Println(strings[i])
	}
}

func outputInts() {
	defer wg.Done()
	for i := 0; i < 5; i++ {
		delay := 1 + rand.Intn(3)
		time.Sleep(time.Duration(delay) * time.Second)
		fmt.Println(i)
	}
}

func main() {
	wg.Add(2)
	go outputStrings()
	go outputInts()
	wg.Wait() // 会阻塞等待结束
}

channel 可以用来同步协程之间的运行

死锁的情况:

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func pingGenerator(c chan string) {
	defer wg.Done()
	for i := 0; i < 5; i++ {
		c <- "ping"
		time.Sleep(time.Second * 1)
	}
}

func output(c chan string) {
	defer wg.Done()
	for {
		value := <-c
		fmt.Println(value)
	}
}

func main() {
	c := make(chan string)
	wg.Add(2)
	go pingGenerator(c)
	go output(c)
	wg.Wait() // 会阻塞等待结束
}

这里output会在5个ping以后一直阻塞,造成整个程序的阻塞,导致所有的协程无法继续下去,造成了死锁。

解决的方法:

使用select语句:

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func pingGenerator(c chan string) {
	defer wg.Done()
	for i := 0; i < 5; i++ {
		c <- "ping"
		time.Sleep(time.Second * 1)
	}
}

func output(c chan string) {
	defer wg.Done()
	for {
		select {
		case value := <-c:
			fmt.Println(value)
		case <-time.After(time.Second * 3):
			fmt.Println("Program timed out.")
			wg.Done()
		}

	}
}

func main() {
	c := make(chan string)
	wg.Add(2)
	go pingGenerator(c)
	go output(c)
	wg.Wait() // 会阻塞等待结束
}

使用另一个channel,避免使用WaitGroup:

package main

import (
	"fmt"
	"time"
)

var quit = make(chan bool)

func pingGenerator(c chan string) {
	for i := 0; i < 5; i++ {
		c <- "ping"
		time.Sleep(time.Second * 1)
	}
}

func output(c chan string) {
	for {
		select {
		case value := <-c:
			fmt.Println(value)
		case <-time.After(time.Second * 3):
			fmt.Println("Program timed out.")
			quit <- true
		}

	}
}

func main() {
	c := make(chan string)

	go pingGenerator(c)
	go output(c)
	<-quit
}

channel的缓冲区

如果不指定 channel 的缓冲区,那么只有一个,在往里面塞数据的时候,程序是会阻塞的

比如,如下代码会直接导致死锁,因为缓冲区已经满了

package main

var unbuf_chan = make(chan string)

func main() {
	unbuf_chan <- "hello world"
}

下面的代码一样会死锁,因为缓冲区满了,这里需要比较小心,避免缓冲区满了的情况,及时消费掉(从channel取出来)

package main

var unbuf_chan = make(chan string, 1)

func main() {
	unbuf_chan <- "hello world"
	unbuf_chan <- "hello world"
}

Race Condition

类似线程不安全的情况:

package main

import (
	"fmt"
	"sync"
)

const (
	number = 1000
)

var countValue int

func main() {
	var wg sync.WaitGroup
	wg.Add(number)
	for i := 0; i < number; i++ {
		go func() {
			countValue++
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Printf("countValue is %d\n", countValue)
}

可以通过 Mutex加锁解决:

package main

import (
	"fmt"
	"sync"
)

const (
	number = 1000
)

var countValue int
var m sync.Mutex

func main() {
	var wg sync.WaitGroup
	wg.Add(number)
	for i := 0; i < number; i++ {
		go func() {
			m.Lock() // 加锁 锁住临界区
			countValue++
			m.Unlock() // 释放锁
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Printf("countValue is %d\n", countValue)
}

两个协程交替打印A和B的实现, 交替打印100个A和B

这个经典问题,使用channel和select 一起实现

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup
var chan_a = make(chan struct{})
var chan_b = make(chan struct{})

func printA() {
	for {
		select {
		case <-chan_b:
			fmt.Println("A")
			chan_a <- struct{}{}
		}
	}
}

func printB() {
	i := 1
	for {
		select {
		case <-chan_a:
			fmt.Printf("%d: B\n", i)
			chan_b <- struct{}{}
		}

		if i == 100 {
			wg.Done()
			return
		}
		i++
	}
}

func main() {
	wg.Add(1)

	go printA()
	go printB()

	chan_a <- struct{}{}

	wg.Wait()
}

这里 主方法中 如果

	chan_a <- struct{}{}

放在

go printA()

之前 就会造成死锁,原因就是 channel 没有缓冲区,会阻塞住。

可以把 chan_a 设置为

var chan_a = make(chan struct{}, 1)

如下代码,就不会死锁了:

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup
var chan_a = make(chan struct{}, 1)
var chan_b = make(chan struct{})

func printA() {
	for {
		select {
		case <-chan_b:
			fmt.Println("A")
			chan_a <- struct{}{}
		}
	}
}

func printB() {
	i := 1
	for {
		select {
		case <-chan_a:
			fmt.Printf("%d: B\n", i)
			chan_b <- struct{}{}
		}

		if i == 100 {
			wg.Done()
			return
		}
		i++
	}
}

func main() {
	wg.Add(1)
	chan_a <- struct{}{}
	go printA()
	go printB()

	wg.Wait()
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值