为什么在Go语言中互斥锁比通道慢?

huangapple go评论74阅读模式
英文:

Why mutexes are slower than channels in golang?

问题

我正在制作一个程序,用于爬取网站并返回它们的状态。

我用不同的方法编写了这个程序。第一种方法使用互斥锁来防止并发写入映射,以便我可以消除数据竞争。然后为了达到相同的目的,我使用通道来实现它。但是当我进行基准测试时,我意识到使用通道实现的速度比使用互斥锁实现的速度要快得多。我想知道为什么会这样?为什么互斥锁性能不佳?我在使用互斥锁时是否做错了什么?

基准测试结果:

为什么在Go语言中互斥锁比通道慢?

代码

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:

为什么在Go语言中互斥锁比通道慢?

Code

package concurrency

import &quot;sync&quot;

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 &lt;- result{u, wc(u)}
		}(url)
	}
	for i := 0; i &lt; len(urls); i++ {
		r := &lt;-resultChannel
		results[r.string] = r.bool
	}
	return results
}

Test code

package concurrency

import (
	&quot;reflect&quot;
	&quot;testing&quot;
	&quot;time&quot;
)

func mockWebsiteChecker(url string) bool {
	time.Sleep(20 * time.Millisecond)
	if url == &quot;https://localhost:3000&quot; {
		return false
	}
	return true
}

func TestCheckWebsites(t *testing.T) {
	websites := []string{
		&quot;https://google.com&quot;,
		&quot;https://localhost:3000&quot;,
		&quot;https://blog.gypsydave5.com&quot;,
	}
	want := map[string]bool{
		&quot;https://google.com&quot;:          true,
		&quot;https://blog.gypsydave5.com&quot;: true,
		&quot;https://localhost:3000&quot;:      false,
	}
	got := CheckWebsites(mockWebsiteChecker, websites)
	if !reflect.DeepEqual(got, want) {
		t.Errorf(&quot;got %v, want %v&quot;, got, want)
	}
}

func BenchmarkCheckWebsites(b *testing.B) {
	urls := make([]string, 1000)
	for i := 0; i &lt; len(urls); i++ {
		urls[i] = &quot;a url&quot;
	}
	b.ResetTimer()
	for i := 0; i &lt; b.N; i++ {
		CheckWebsites(mockWebsiteChecker, urls)
	}
}

func BenchmarkCheckWebsitesChannel(b *testing.B) {
	urls := make([]string, 1000)
	for i := 0; i &lt; len(urls); i++ {
		urls[i] = &quot;a url&quot;
	}
	b.ResetTimer()
	for i := 0; i &lt; 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.

huangapple
  • 本文由 发表于 2023年1月3日 18:52:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/74992364.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定