英文:
Why mutexes are slower than channels in golang?
问题
我正在制作一个程序,用于爬取网站并返回它们的状态。
我用不同的方法编写了这个程序。第一种方法使用互斥锁来防止并发写入映射,以便我可以消除数据竞争。然后为了达到相同的目的,我使用通道来实现它。但是当我进行基准测试时,我意识到使用通道实现的速度比使用互斥锁实现的速度要快得多。我想知道为什么会这样?为什么互斥锁性能不佳?我在使用互斥锁时是否做错了什么?
基准测试结果:
代码
package concurrency
import "sync"
type WebsiteChecker func(string) bool
type result struct {
string
bool
}
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
var wg sync.WaitGroup
var mu sync.Mutex
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
mu.Lock()
results[u] = wc(u)
mu.Unlock()
}(url)
}
wg.Wait()
return results
}
func CheckWebsitesChannel(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++ {
r := <-resultChannel
results[r.string] = r.bool
}
return results
}
测试代码
package concurrency
import (
"reflect"
"testing"
"time"
)
func mockWebsiteChecker(url string) bool {
time.Sleep(20 * time.Millisecond)
if url == "https://localhost:3000" {
return false
}
return true
}
func TestCheckWebsites(t *testing.T) {
websites := []string{
"https://google.com",
"https://localhost:3000",
"https://blog.gypsydave5.com",
}
want := map[string]bool{
"https://google.com": true,
"https://blog.gypsydave5.com": true,
"https://localhost:3000": false,
}
got := CheckWebsites(mockWebsiteChecker, websites)
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}
func BenchmarkCheckWebsites(b *testing.B) {
urls := make([]string, 1000)
for i := 0; i < len(urls); i++ {
urls[i] = "a url"
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
CheckWebsites(mockWebsiteChecker, urls)
}
}
func BenchmarkCheckWebsitesChannel(b *testing.B) {
urls := make([]string, 1000)
for i := 0; i < len(urls); i++ {
urls[i] = "a url"
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
CheckWebsitesChannel(mockWebsiteChecker, urls)
}
}
英文:
I am making a program that crawls websites and return status of them.
I wrote this program with different approaches. The first one using mutexes to prevent concurrent writes to map so that I can get rid of the data race. Then for the same purpose, I implement it with channels. But when I was doing benchmarks I realized that implementing it with channels much faster than implementing it mutexes. I was wondering why it is happening? Why mutexes lacks of performance? am I doing something wrong with mutexes?
Benchmark result:
Code
package concurrency
import "sync"
type WebsiteChecker func(string) bool
type result struct {
string
bool
}
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
var wg sync.WaitGroup
var mu sync.Mutex
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
mu.Lock()
results[u] = wc(u)
mu.Unlock()
}(url)
}
wg.Wait()
return results
}
func CheckWebsitesChannel(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++ {
r := <-resultChannel
results[r.string] = r.bool
}
return results
}
Test code
package concurrency
import (
"reflect"
"testing"
"time"
)
func mockWebsiteChecker(url string) bool {
time.Sleep(20 * time.Millisecond)
if url == "https://localhost:3000" {
return false
}
return true
}
func TestCheckWebsites(t *testing.T) {
websites := []string{
"https://google.com",
"https://localhost:3000",
"https://blog.gypsydave5.com",
}
want := map[string]bool{
"https://google.com": true,
"https://blog.gypsydave5.com": true,
"https://localhost:3000": false,
}
got := CheckWebsites(mockWebsiteChecker, websites)
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}
func BenchmarkCheckWebsites(b *testing.B) {
urls := make([]string, 1000)
for i := 0; i < len(urls); i++ {
urls[i] = "a url"
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
CheckWebsites(mockWebsiteChecker, urls)
}
}
func BenchmarkCheckWebsitesChannel(b *testing.B) {
urls := make([]string, 1000)
for i := 0; i < len(urls); i++ {
urls[i] = "a url"
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
CheckWebsitesChannel(mockWebsiteChecker, urls)
}
}
答案1
得分: 3
我觉得使用互斥锁版本的代码不仅保护了results
映射,还保护了wc
(只有在获取锁之后才能进行调用,因此实际上是对调用进行了串行化)。而使用通道发送只在右侧准备好后才锁定通道,因此可以并发地调用wc
。可以看到像下面这样的代码:
go func(u string) {
defer wg.Done()
r := wc(u)
mu.Lock()
results[u] = r
mu.Unlock()
}(url)
使用互斥锁的性能更好。
英文:
It seems to me that with the mutex version of the code you're not only protecting the results
map but the wc
too (the call can only take place once the lock has been acquired so effectively you are serializing the calls). Send to chan only locks the channel once the right side is ready so calls to wc
can happen concurrently. See does code like
go func(u string) {
defer wg.Done()
r := wc(u)
mu.Lock()
results[u] = r
mu.Unlock()
}(url)
perform better with mutex.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论