Learn Go with tests 学习笔记(10)——Concurrency

Concurrency

目标功能:

type WebsiteChecker func(string) bool//输入单个网址,返回状态

func CheckWebsites(WebsiteChecker, []string) map[string]bool {}//输入网址切片,返回状态map

使用依赖注入dependency injection

测试函数的同时避免进行实际的http calls,测试调用CheckWebsites函数时传入的WebsiteChecker使用下面自己定义的函数mockWebsiteChecker。在这里依赖注入使得我们的CheckWebsites函数不再依赖于某个固定的WebsiteChecker,mocking使得我们不需要真正地去访问这些网址,但是依然能够监视函数WebsiteChecker函数是否被正确调用。

func mockWebsiteChecker(url string) bool {
	if url == "waat://furhurterwe.geds" {
		return false
	}
	return true
}

基准测试bench mark

为了测试函数性能,使用benchmark test基准测试。这里同样首先定制一个有固定时延的WebsiteChecker依赖,将它注入到基准测试中。在这个定制的依赖中,我们不需要知道输入的是什么网站,只关心函数性能问题。因此假定判断每个网站状态需要的时间为20毫秒。

func slowStubWebsiteChecker(_ string) bool {
	time.Sleep(20 * time.Millisecond)
	return true
}

func BenchmarkCheckWebsites(b *testing.B) {
	urls := make([]string, 100)
	for i := 0; i < len(urls); i++ {
		urls[i] = "a url"
	}
	b.ResetTimer()//这一步很重要,我们只测试CheckWebsites的性能所以需要reset时间
	for i := 0; i < b.N; i++ {
		CheckWebsites(slowStubWebsiteChecker, urls)
	}
}
go test -bench="."
pkg: github.com/gypsydave5/learn-go-with-tests/concurrency/v0
BenchmarkCheckWebsites-4               1        2249228637 ns/op
PASS
ok      github.com/gypsydave5/learn-go-with-tests/concurrency/v0        2.268s

并发concurrency

concurrency —— having more than one thing in progress

Instead of waiting for a website to respond before sending a request to the next website, we will tell our computer to make the next request while it is waiting.

并发不是指同时干很多事情,而是指在某件事的进行过程中利用余裕去干其他事情。很多事情同时进行是并行。

  • 阻塞 blocking:指调用某个函数之后等待它返回或结束执行的过程
  • 协程 goroutine:在Go中不会阻塞的操作,在独立的进程中运行。(进程可以理解为从上到下执行Go代码的过程,每调用一个函数就进入并且执行该函数的代码;每当一个独立的进程开始,就相当于一个新的执行者在不打断之前执行者执行代码的同时,开始独立的执行过程)
  • 开启goroutine的方法:go 函数名(参数)
package concurrency

type WebsiteChecker func(string) bool

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
	results := make(map[string]bool)

	for _, url := range urls {
		go func() {
			results[url] = wc(url)
		}()
	}

	return results
}

匿名函数

第9行使用了匿名函数作为goroutine的执行者。匿名函数具有两个有用的特性:

  1. 可以同时进行声明和执行,第11行的()就是在执行函数
  2. 可以在函数体内部直接地址引用所有当前环境下的变量。如果你想值引用这些变量的话可以将它的类型写到形参列表里,再在()中取用即可

Welcome to concurrency: when it’s not handled correctly it’s hard to predict what’s going to happen. 当我们再次go test,会发现程序运行出错了,我们并没有得到我们想要的网站状态map。这是因为

  1. 我们的主程序的进程在开启三个goroutine之后并没有等待它们执行完毕就继续执行下去,导致还没来得及将所有wc(url)结果赋值给results[url]就过早地进行结果判断
  2. 我们没有一个很好的机制控制三个goroutine按照次序进行赋值。在这个过程中可能会发生panic,因为两个goroutine同时对map进行写操作产生了race condition
  3. 还有一个可能出错的地方在于url以地址传递的方式传入匿名函数,在并发执行三个goroutine的过程中,有可能第一个goroutine还没做完,url已经变成了第三个网址,这就导致最终map里只存储了最后一个网址的状态。

解决方法:传入形参(值传递网址变量)、使用channel协调进程间通信

channels 是一个 Go 数据结构,可以同时接收和发送值。这些操作以及细节允许不同进程之间的通信。在这种情况下,我们想要考虑父进程和每个 goroutine 之间的通信。channel遵循==先入先出==原则。

type WebsiteChecker func(string) bool
type result struct {
    string
    bool
}

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
    results := make(map[string]bool)
    resultChannel := make(chan result)

    for _, url := range urls {
        go func(u string) {
            resultChannel <- result{u, wc(u)}
        }(url)
    }

    for i := 0; i < len(urls); i++ {
        result := <-resultChannel
        results[result.string] = result.bool
    }

    return results
}
// Send statement
resultChannel <- result{u, wc(u)}
// Receive expression
r := <-resultChannel
pkg: github.com/gypsydave5/learn-go-with-tests/concurrency/v2
BenchmarkCheckWebsites-8             100          23406615 ns/op
PASS
ok      github.com/gypsydave5/learn-go-with-tests/concurrency/v2        2.377s

总结

  • goroutines 是 Go 的基本并发单元,它让我们可以同时检查多个网站。
  • anonymous functions(匿名函数),我们用它来启动每个检查网站的并发进程。
  • channels,用来组织和控制不同进程之间的交流,使我们能够避免 race condition(竞争条件) 的问题。
  • the race detector(竞争探测器) 帮助我们调试并发代码的问题。

过早的优化是万恶之源,我们应当先确保程序的正确性再考虑加速

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洞爷湖dyh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值