分享
  1. 首页
  2. 文章

《The.Go.Programming.Language.2015.11》之Goroutines

KingEasternSun · · 1463 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

并行处理图片并等待全部图片处理结束

func makeThumbnails6(filenames <-chan string) int64 {
 sizes := make(chan int64)
 var wg sync.WaitGroup // number of working goroutines
 for f := range filenames {
 wg.Add(1)
 // worker
 go func(f string) {
 defer wg.Done()
 thumb, err := thumbnail.ImageFile(f)
 if err != nil {
 log.Println(err)
 return
 }
 info, _ := os.Stat(thumb) // OK to ignore error
 sizes <- info.Size()
 }(f)
 }
 // closer
 go func() {
 wg.Wait()
 close(sizes)
 }()
 var total int64
 for size := range sizes {
 total += size
 }
 return total
}

该代码中var wg sync.WaitGroup用于实现等待全部图片处理结束。
函数makeThumbnails6在执行到

for size := range sizes {

时会阻塞,直到

 go func() {
 wg.Wait()
 close(sizes)
 }()

执行到close(sizes)。该goroutine先阻塞在wg.Wait()上,直到所有的图片处理结束,图片处理结束时会调用wg.Done()

限定并发数量

最简单的是通过带缓存的通道实现
如下,在执行之前调用tokens <- struct{}{}获取一个名额,执行结束后调用<-tokens释放名额。

// tokens is a counting semaphore used to
// enforce a limit of 20 concurrent requests.
var tokens = make(chan struct{}, 20)
func crawl(url string) []string {
 fmt.Println(url)
 tokens <- struct{}{} // acquire a token
 list, err := links.Extract(url)
 <-tokens // release the token
 if err != nil {
 log.Print(err)
 }
 return list
}

复用select

time.Tick创建一个通道,每隔一段时间往通道里写入数据。

func main() {
 fmt.Println("Commencing countdown.")
 tick := time.Tick(1 * time.Second)
 for countdown := 10; countdown > 0; countdown-- {
 fmt.Println(countdown)
 <-tick
 }
 launch()
}

等待多个通道时使用select

select {
case <-ch1:
// ...
case x := <-ch2:
// ...use x...
case ch3 <- y:
// ...
default:
// ...
}

select阻塞直到某个通道可用,如果多个同时可用,就随机选一个。

time.After在经过指定时间后生成一个通道,同时产生一个goroutine往这个通道里写入一个值。

func main() {
 // ...create abort channel...
 fmt.Println("Commencing countdown. Press return to abort.")
 select {
 case <-time.After(10 * time.Second):
 // Do nothing.
 case <-abort:
 fmt.Println("Launch aborted!")
 return
 }
 launch()
}

定时器的常用使用方式为:

ticker := time.NewTicker(1 * time.Second)
<-ticker.C // receive from the ticker's channel
ticker.Stop() // cause the ticker's goroutine to terminate

非阻塞模式


select {
 case <-abort:
 fmt.Printf("Launch aborted!\n")
 return
 default:
 // do nothing
}

channel的0值是nil。在nil的通道上读写会一直阻塞。select中case

互斥锁

var (
 mu
 sync.Mutex // guards balance
 balance int
)
func Deposit(amount int) {
 mu.Lock()
 balance = balance + amount
 mu.Unlock()
}
func Balance() int {
 mu.Lock()
 defer mu.Unlock()
 return balance
}

文中又举了这样一个例子,一个人如果账户余额不足的话就会回退之前的操作,Deposit(-amount)表示购买操作, Balance()判断余额不足:

// NOTE: not atomic!
func Withdraw(amount int) bool {
 Deposit(-amount)
 if Balance() < 0 {
 Deposit(amount)
 return false // insufficient funds
 }
 return true
}

这个代码的问题在于,假如A有100元,想要购买300的车,Deposit(-amount)造成了Balance()为负数,那么如果在Deposit(amount)之前A打算买小于100元的生活用品也无法购买,因为此时Balance()还为负数,还没有恢复。
所以要把购买和恢复作为一个原子操作。

func Withdraw(amount int) bool {
 mu.Lock()
 defer mu.Unlock()
 deposit(-amount)
 if balance < 0 {
 deposit(amount)
 return false // insufficient funds
 }
 return true
}
func Deposit(amount int) {
 mu.Lock()
 defer mu.Unlock()
 deposit(amount)
}
func Balance() int {
 mu.Lock()
 defer mu.Unlock()
 return balance
}
// This function requires that the lock be held.
func deposit(amount int) {
 balance += amount
}

sync.Once

首先我们看以下代码

var icons map[string]image.Image
func loadIcons() {
 icons = map[string]image.Image{
 "spades.png": loadIcon("spades.png"),
 "hearts.png": loadIcon("hearts.png"),
 "diamonds.png": loadIcon("diamonds.png"),
 "clubs.png": loadIcon("clubs.png"),
 }
}
// NOTE: not concurrency-safe!
func Icon(name string) image.Image {
 if icons == nil {
 loadIcons() // one-time initialization
 }
 return icons[name]
}

其中函数Iconnot concurrency-safe的,假如有两个Goroutines A和B,A第一次执行到if icons == nil然后切换到B,B此时判断if icons == nil仍然为真,这样loadIcons就执行了两遍。

解决方法一,使用互斥锁:

var mu sync.Mutex // guards icons
// Concurrency-safe.
func Icon(name string) image.Image {
 mu.Lock()
 defer mu.Unlock()
 if icons == nil {
 loadIcons()
 }
 return icons[name]
}

但是问题在于icons被初始化之后,后续的读取操作不能并行执行。

解决方法二 使用读写锁

var mu sync.RWMutex // guards icons
var icons map[string]image.Image
// Concurrency-safe.
func Icon(name string) image.Image {
 mu.RLock()
 if icons != nil {
 icon := icons[name]
 mu.RUnlock()
 return icon
 }
 mu.RUnlock()
 // acquire an exclusive lock
 mu.Lock()
 if icons == nil { // NOTE: must recheck for nil
 loadIcons()
 }
 icon := icons[name]
 mu.Unlock()
 return icon
}

这样的问题就是耗费太大

解决方法三 使用sync.Once

var loadIconsOnce sync.Once
var icons map[string]image.Image
// Concurrency-safe.
func Icon(name string) image.Image {
 loadIconsOnce.Do(loadIcons)
 return icons[name]
}

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:KingEasternSun

查看原文:《The.Go.Programming.Language.2015.11》之Goroutines

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1463 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏