golang channel 和 mutex 及原子操作 用于并发控制的性能对比

场景:
对同个数加 10w 次,看耗费时间,这里没有用 benchmark 测试,在意的请略过。

以下是测试代码:

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)


func TestCount(t *testing.T) {
	var cnt int
	var wg sync.WaitGroup
	numsWorkers := 100000
	start := time.Now()

	for i := 0; i < numsWorkers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			cnt++
		}()
	}

	wg.Wait()
	fmt.Printf("Result, cnt: %d, time cost: %v\n", cnt, time.Now().Sub(start))
}


func TestCountWithMutex(t *testing.T) {
	var cnt int
	var wg sync.WaitGroup
	var mu sync.Mutex
	numsWorkers := 100000
	start := time.Now()

	for i := 0; i < numsWorkers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			mu.Lock()
			defer mu.Unlock()
			cnt++
		}()
	}

	wg.Wait()
	fmt.Printf("Result, cnt: %d, time cost: %v\n", cnt, time.Now().Sub(start))
}

func TestCountWithChannel(t *testing.T) {
	var cnt int
	var wg sync.WaitGroup
	lock := make(chan struct{}, 1) // cap > 1, unsafe
	numsWorkers := 100000
	start := time.Now()

	for i := 0; i < numsWorkers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			lock <- struct{}{}

			cnt++
			<- lock
		}()
	}

	wg.Wait()
	fmt.Printf("Result, cnt: %d, time cost: %v\n", cnt, time.Now().Sub(start))
}

func TestCountWithAtomic(t *testing.T) {
	var cnt int64
	var wg sync.WaitGroup
	numsWorkers := 100000
	start := time.Now()

	for i := 0; i < numsWorkers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			atomic.AddInt64(&cnt, 1)
		}()
	}

	wg.Wait()
	fmt.Printf("Result, cnt: %d, time cost: %v\n", cnt, time.Now().Sub(start))
}

以下实测实结果:

$ go test -v
=== RUN   TestCount
Result, cnt: 98431, time cost: 24.45ms
--- PASS: TestCount (0.02s)
=== RUN   TestCountWithMutex
Result, cnt: 100000, time cost: 38.1108ms
--- PASS: TestCountWithMutex (0.04s)
=== RUN   TestCountWithChannel
Result, cnt: 100000, time cost: 102.0231ms
--- PASS: TestCountWithChannel (0.10s)
=== RUN   TestCountWithAtomic
Result, cnt: 100000, time cost: 26.5348ms
--- PASS: TestCountWithAtomic (0.03s)
PASS
ok      go-test 0.235s

多次测试后,从性能对比看:原子操作 > mutex > channel,三者都可以保证并发的安全性,但在性能上,mutex 也没有那么差,并且比 channel 很很多,原子操作通过 cpu操作的原子性,实现无锁,但主要适合于一些单值的比较增减,对于操作序列来看,适合 mutex 及 channel,由于 golang 本身的互斥锁有一定自旋特性,对比单纯的公平锁还是有一定提升,所以个人认为 mutex 不一定比 channel 差。

posted on 2024-07-24 10:09  进击的davis  阅读(1)  评论(0编辑  收藏  举报

导航