慕桂英546537
实际上,Go 确实是并行写入磁盘的。问题中顺序行为的原因是math/rand。此包使用通过内部互斥锁实现的线程安全Read随机数生成器:请参阅和globalRndlockedSource这就是为什么你的GenerateFilegoroutines 几乎严格地一个接一个地运行——它们在rand.globalRnd.lk互斥锁上进行内部同步。有两种方法可以提高性能。一种是在每个线程中使用独立的PRNG,另一种是预先生成写入数据。这是一个尝试所有变体的示例程序。package mainimport ( "fmt" "io/ioutil" "log" "math/rand" "os" "sort" "sync" "time")var wg sync.WaitGroupconst N = 30var elapsed_g [N]time.Durationfunc SortAndLogElapsed(prefix string) { sort.Slice(elapsed_g[:], func(i, j int) bool { return elapsed_g[i].Nanoseconds() < int64(elapsed_g[j].Nanoseconds()) }) for _, elapsed := range elapsed_g { fmt.Println(prefix, elapsed) }}func GenerateFile(start time.Time, id int) error { defer wg.Done() elapsed := time.Since(start) buf := make([]byte, 7500000) rand.Read(buf) // generate random data randomFileName := fmt.Sprintf("/tmp/gotest-%v", rand.Int()) err := ioutil.WriteFile(randomFileName, buf, 0666) if err != nil { return err } defer os.Remove(randomFileName) elapsed = time.Since(start) // log.Printf("generate file %s done in %s", randomFileName, elapsed) elapsed_g[id] = elapsed return nil}func RunWithCommonPrng() { start := time.Now() for i := 0; i < N; i++ { wg.Add(1) go GenerateFile(start, i) } wg.Wait() elapsed := time.Since(start) SortAndLogElapsed("common PRNG: ") log.Printf("done in %s", elapsed)}func GenerateFilePrivatePrng(id int, prng rand.Source, start time.Time) error { defer wg.Done() elapsed := time.Since(start) buf := make([]byte, 7500000) rand.New(prng).Read(buf) // generate random data randomFileName := fmt.Sprintf("/tmp/gotest-%v", prng.Int63()) err := ioutil.WriteFile(randomFileName, buf, 0666) if err != nil { return err } defer os.Remove(randomFileName) elapsed = time.Since(start) elapsed_g[id] = elapsed // log.Printf("generate file %s with private source: done in %s", randomFileName, elapsed) return nil}func RunWithPrivatePrng() { start := time.Now() for i := 0; i < N; i++ { wg.Add(1) go GenerateFilePrivatePrng(i, rand.NewSource(int64(i)), start) } wg.Wait() elapsed := time.Since(start) SortAndLogElapsed("Private PRNG: ") log.Printf("done in %s", elapsed)}func GenerateFileWithGivenData(id int, buf []byte, start time.Time) error { defer wg.Done() randomFileName := fmt.Sprintf("/tmp/gotest-%v", rand.Int()) err := ioutil.WriteFile(randomFileName, buf, 0666) if err != nil { return err } defer os.Remove(randomFileName) elapsed := time.Since(start) elapsed_g[id] = elapsed // log.Printf("generate file %s with data: done in %s", randomFileName, elapsed) return nil}func RunWithCommonData() { buf := make([]byte, 7500000) rand.Read(buf) // generate random data start := time.Now() for i := 0; i < N; i++ { wg.Add(1) go GenerateFileWithGivenData(i, buf, start) } wg.Wait() elapsed := time.Since(start) SortAndLogElapsed("Common data: ") log.Printf("done in %s", elapsed)}func main() { log.Printf("Used CPUs / Max CPUs: %d/%d", runtime.GOMAXPROCS(0), runtime.NumCPU()) RunWithCommonPrng() RunWithPrivatePrng() RunWithCommonData()}在 8 核 CPU 和 SSD 上的输出是这样的:2022/10/02 00:00:08 Used CPUs / Max CPUs: 8/8common PRNG: 9.943335mscommon PRNG: 15.12122mscommon PRNG: 20.856216mscommon PRNG: 26.636462mscommon PRNG: 32.041066mscommon PRNG: 37.450744mscommon PRNG: 43.286644mscommon PRNG: 48.695199mscommon PRNG: 54.518533mscommon PRNG: 59.858065mscommon PRNG: 65.620084mscommon PRNG: 71.111171mscommon PRNG: 76.388583mscommon PRNG: 81.609326mscommon PRNG: 87.465878mscommon PRNG: 92.623557mscommon PRNG: 98.35468mscommon PRNG: 103.606529mscommon PRNG: 109.28623mscommon PRNG: 114.981873mscommon PRNG: 120.26626mscommon PRNG: 125.530811mscommon PRNG: 131.222195mscommon PRNG: 136.399946mscommon PRNG: 142.305635mscommon PRNG: 147.687525mscommon PRNG: 153.002392mscommon PRNG: 158.769948mscommon PRNG: 164.241503mscommon PRNG: 169.531355ms2022/10/02 00:00:08 done in 170.273377msPrivate PRNG: 16.255543msPrivate PRNG: 17.155624msPrivate PRNG: 17.477437msPrivate PRNG: 17.49527msPrivate PRNG: 17.521759msPrivate PRNG: 18.363554msPrivate PRNG: 19.800906msPrivate PRNG: 30.340522msPrivate PRNG: 31.551496msPrivate PRNG: 40.583626msPrivate PRNG: 54.682705msPrivate PRNG: 54.832006msPrivate PRNG: 54.983126msPrivate PRNG: 55.143073msPrivate PRNG: 56.517272msPrivate PRNG: 56.577967msPrivate PRNG: 57.718msPrivate PRNG: 58.770033msPrivate PRNG: 59.246808msPrivate PRNG: 59.608246msPrivate PRNG: 59.789123msPrivate PRNG: 60.028814msPrivate PRNG: 68.533662msPrivate PRNG: 69.606317msPrivate PRNG: 69.837988msPrivate PRNG: 71.488161msPrivate PRNG: 71.770842msPrivate PRNG: 72.036881msPrivate PRNG: 72.23509msPrivate PRNG: 73.037337ms2022/10/02 00:00:08 done in 73.694825msCommon data: 5.220506msCommon data: 5.220523msCommon data: 5.220524msCommon data: 5.220526msCommon data: 5.221125msCommon data: 5.221169msCommon data: 5.222472msCommon data: 6.977304msCommon data: 13.601358msCommon data: 13.614532msCommon data: 13.859067msCommon data: 14.75378msCommon data: 16.00253msCommon data: 16.111086msCommon data: 16.263291msCommon data: 16.42076msCommon data: 17.024946msCommon data: 17.313631msCommon data: 17.749351msCommon data: 18.18497msCommon data: 18.83511msCommon data: 21.789867msCommon data: 22.308659msCommon data: 22.308701msCommon data: 22.546815msCommon data: 23.298865msCommon data: 23.482138msCommon data: 23.610855msCommon data: 23.667347msCommon data: 24.500486ms2022/10/02 00:00:08 done in 25.205652ms“公共数据”用于预生成的缓冲区。它表明 Golang 确实并行写入磁盘。它在线程之间分配 goroutines,这些 goroutines 占用 CPU 核心,直到 I/O 完成。更新这是打印 Linux 线程 ID 和 CPU 编号的代码。package main/*#define _GNU_SOURCE#include <sched.h>*/import "C"import ( "fmt" "io/ioutil" "log" "math/rand" "os" "runtime" "sort" "sync" "syscall" "time" "github.com/pkg/profile")func GetCpu() int { var ret C.int = C.sched_getcpu() return int(ret)}func GetThreadId() int { return syscall.Gettid()}var wg sync.WaitGroupconst N = 30var elapsed_g [N]time.Durationfunc SortAndLogElapsed(prefix string) { sort.Slice(elapsed_g[:], func(i, j int) bool { return elapsed_g[i].Nanoseconds() < int64(elapsed_g[j].Nanoseconds()) }) for _, elapsed := range elapsed_g { fmt.Println(prefix, elapsed) }}func GenerateFileWithGivenData(id int, buf []byte, start time.Time) error { defer wg.Done() randomFileName := fmt.Sprintf("/tmp/gotest-%v", rand.Int()) tid := GetThreadId() cpu := GetCpu() before := time.Now() fmt.Printf("Before WriteFile:\t----\t%d\t%d\t%d\t%s\n", id, tid, cpu, before.String()) err := ioutil.WriteFile(randomFileName, buf, 0666) after := time.Now() tid = GetThreadId() cpu = GetCpu() fmt.Printf("After WriteFile:\t%d\t%d\t%d\t%d\t%s\n", after.Sub(before).Microseconds(), id, tid, cpu, after.String()) if err != nil { return err } defer os.Remove(randomFileName) elapsed := time.Since(start) elapsed_g[id] = elapsed // log.Printf("generate file %s with data: done in %s", randomFileName, elapsed) return nil}func RunWithCommonData() { buf := make([]byte, 7500000) rand.Read(buf) // generate random data fmt.Printf(" \tElapsed\tG\tTID\tCPU\ttime\n") start := time.Now() println("") for i := 0; i < N; i++ { wg.Add(1) go GenerateFileWithGivenData(i, buf, start) } wg.Wait() elapsed := time.Since(start) SortAndLogElapsed("Common data: ") log.Printf("done in %s", elapsed)}func main() { log.Printf("Used CPUs / Max CPUs: %d/%d", runtime.GOMAXPROCS(0), runtime.NumCPU()) // RunWithCommonPrng() // RunWithPrivatePrng() defer profile.Start(profile.CPUProfile).Stop() RunWithCommonData()}我系统的输出是(G internal goroutine ID, TID - Linux thread id, CPU - CPU number, last column is elapsed time)按时间排序: Elapsed G TID CPU timeBefore WriteFile: ---- 29 23379 0 2022-10-03 20:24:47.35247545 +0900 KST m=+0.006016977Before WriteFile: ---- 0 23380 1 2022-10-03 20:24:47.352475589 +0900 KST m=+0.006017128Before WriteFile: ---- 14 23383 7 2022-10-03 20:24:47.352506383 +0900 KST m=+0.006047950Before WriteFile: ---- 7 23381 2 2022-10-03 20:24:47.352572666 +0900 KST m=+0.006114235Before WriteFile: ---- 10 23377 6 2022-10-03 20:24:47.352634156 +0900 KST m=+0.006175692Before WriteFile: ---- 8 23384 4 2022-10-03 20:24:47.352727575 +0900 KST m=+0.006269119Before WriteFile: ---- 9 23385 5 2022-10-03 20:24:47.352766795 +0900 KST m=+0.006308348After WriteFile: 4133 14 23383 7 2022-10-03 20:24:47.356640341 +0900 KST m=+0.010181880After WriteFile: 4952 7 23381 2 2022-10-03 20:24:47.357525386 +0900 KST m=+0.011066917After WriteFile: 5049 29 23379 0 2022-10-03 20:24:47.357525403 +0900 KST m=+0.011066934After WriteFile: 4758 9 23385 5 2022-10-03 20:24:47.3575254 +0900 KST m=+0.011066928After WriteFile: 4892 10 23377 6 2022-10-03 20:24:47.357526773 +0900 KST m=+0.011068303After WriteFile: 5051 0 23380 1 2022-10-03 20:24:47.35752678 +0900 KST m=+0.011068311After WriteFile: 4801 8 23384 4 2022-10-03 20:24:47.357529101 +0900 KST m=+0.011070629Before WriteFile: ---- 12 23380 1 2022-10-03 20:24:47.357554923 +0900 KST m=+0.011096462Before WriteFile: ---- 13 23377 6 2022-10-03 20:24:47.357555161 +0900 KST m=+0.011096695Before WriteFile: ---- 1 23381 2 2022-10-03 20:24:47.357555163 +0900 KST m=+0.011096697Before WriteFile: ---- 2 23381 2 2022-10-03 20:24:47.35756292 +0900 KST m=+0.011104452Before WriteFile: ---- 11 23377 6 2022-10-03 20:24:47.3575642 +0900 KST m=+0.011105730Before WriteFile: ---- 21 23385 5 2022-10-03 20:24:47.357570038 +0900 KST m=+0.011111568Before WriteFile: ---- 25 23383 7 2022-10-03 20:24:47.357572217 +0900 KST m=+0.011113747Before WriteFile: ---- 26 23379 0 2022-10-03 20:24:47.358768915 +0900 KST m=+0.012310542Before WriteFile: ---- 27 23384 4 2022-10-03 20:24:47.361560776 +0900 KST m=+0.015102306After WriteFile: 4020 25 23383 7 2022-10-03 20:24:47.361593063 +0900 KST m=+0.015134592After WriteFile: 4873 12 23380 1 2022-10-03 20:24:47.362428015 +0900 KST m=+0.015969540After WriteFile: 4858 21 23385 5 2022-10-03 20:24:47.362428103 +0900 KST m=+0.015969632After WriteFile: 4865 2 23381 2 2022-10-03 20:24:47.362428238 +0900 KST m=+0.015969769After WriteFile: 4864 11 23377 6 2022-10-03 20:24:47.362428347 +0900 KST m=+0.015969877Before WriteFile: ---- 15 23385 5 2022-10-03 20:24:47.362454039 +0900 KST m=+0.015995570Before WriteFile: ---- 28 23380 1 2022-10-03 20:24:47.362454041 +0900 KST m=+0.015995573Before WriteFile: ---- 23 23377 6 2022-10-03 20:24:47.362454121 +0900 KST m=+0.015995651Before WriteFile: ---- 16 23385 5 2022-10-03 20:24:47.362462845 +0900 KST m=+0.016004374Before WriteFile: ---- 22 23377 6 2022-10-03 20:24:47.362479715 +0900 KST m=+0.016021242After WriteFile: 4902 26 23379 0 2022-10-03 20:24:47.363671623 +0900 KST m=+0.017213150Before WriteFile: ---- 18 23386 6 2022-10-03 20:24:47.365182522 +0900 KST m=+0.018724057After WriteFile: 8764 13 23383 7 2022-10-03 20:24:47.366320071 +0900 KST m=+0.019861611Before WriteFile: ---- 17 23379 0 2022-10-03 20:24:47.366374805 +0900 KST m=+0.019916338After WriteFile: 4902 27 23384 4 2022-10-03 20:24:47.366463028 +0900 KST m=+0.020004556After WriteFile: 4729 28 23380 1 2022-10-03 20:24:47.367183315 +0900 KST m=+0.020724852After WriteFile: 4720 16 23385 5 2022-10-03 20:24:47.367183317 +0900 KST m=+0.020724850Before WriteFile: ---- 19 23385 5 2022-10-03 20:24:47.367230069 +0900 KST m=+0.020771602Before WriteFile: ---- 20 23384 4 2022-10-03 20:24:47.367748633 +0900 KST m=+0.021290163Before WriteFile: ---- 3 23391 3 2022-10-03 20:24:47.368046383 +0900 KST m=+0.021587923Before WriteFile: ---- 5 23388 1 2022-10-03 20:24:47.36857915 +0900 KST m=+0.022120682Before WriteFile: ---- 4 23380 1 2022-10-03 20:24:47.368590097 +0900 KST m=+0.022131628Before WriteFile: ---- 6 23393 2 2022-10-03 20:24:47.370493582 +0900 KST m=+0.024035118After WriteFile: 10260 22 23377 6 2022-10-03 20:24:47.372740578 +0900 KST m=+0.026282112After WriteFile: 5326 20 23384 4 2022-10-03 20:24:47.37307519 +0900 KST m=+0.026616720After WriteFile: 10922 23 23387 0 2022-10-03 20:24:47.373376163 +0900 KST m=+0.026917695After WriteFile: 5613 3 23391 3 2022-10-03 20:24:47.373660058 +0900 KST m=+0.027201605After WriteFile: 5332 4 23380 1 2022-10-03 20:24:47.373922339 +0900 KST m=+0.027463865After WriteFile: 8871 18 23377 6 2022-10-03 20:24:47.374053982 +0900 KST m=+0.027595513After WriteFile: 7880 17 23384 4 2022-10-03 20:24:47.374255159 +0900 KST m=+0.027796694After WriteFile: 12127 15 23387 0 2022-10-03 20:24:47.37458126 +0900 KST m=+0.028122790After WriteFile: 7422 19 23391 3 2022-10-03 20:24:47.374652483 +0900 KST m=+0.028194020Before WriteFile: ---- 24 23377 6 2022-10-03 20:24:47.375338247 +0900 KST m=+0.028879777After WriteFile: 5111 6 23393 2 2022-10-03 20:24:47.375605341 +0900 KST m=+0.029146871After WriteFile: 19459 1 23392 5 2022-10-03 20:24:47.377014458 +0900 KST m=+0.030555986After WriteFile: 3847 24 23377 6 2022-10-03 20:24:47.379185393 +0900 KST m=+0.032726920After WriteFile: 10778 5 23388 0 2022-10-03 20:24:47.379358058 +0900 KST m=+0.032899584它表明 goroutines 在我的 8 核 CPU 的所有内核上的许多不同线程中运行。看起来最快的 IO 是在那些保留了线程和 CPU 的 goroutine 中。而且似乎停放/取消停放线程会使阻塞 IO 变慢。我用 100 个 goroutines 运行相同的代码。最坏的情况有 60 毫秒那么大,但它不是最后一个,中间的一个。即使在最后,也有 5.5 毫秒的快速写入。