GO 协程【VS】C# 多线程【Go-C# Round 1】
〇、前言
最近接触到 Go 语言相关的内容,由于之前都是用的 C# 语言,然后就萌生了对这两种语言进行多方面比较。
本文将在 Go 的优势项目协程,来与 C# 的多线程操作进行比较,看下差距有多大。
实际上 C# 中也有类似协程的操作,是通过 yield 关键字实现的,等后续再另做对比。
一、准备工作
先准备 1000 个同样内容的 txt 文件,内容是一串简单的字符串,以供程序读取。
程序实现的大体思路:
设置固定数量的协程(Go)或线程(C#),对 1000 个文本文件循环进行操作,取到的文件中的字符串加入到字符串列表中,然后记录耗时情况。
二、Go 语言实现
如下代码,基于(go version go1.21.0 windows/amd64),通过有缓冲的通道 channel,来设置允许同时运行 10 个协程,并通过互斥锁来保证多协程环境中,string 切片的操作安全:
package main import ( "fmt" "io/fs" "os" "sync" "time" ) var mutex sync.Mutex // 互斥锁的声明 func main() { fmt.Println("begin-----------------!") path := "E:\\OA日常文件\\test\\gotestfile" files_entry, err := os.ReadDir(path) // 获取所有文件的路径 if err != nil { fmt.Println("Error reading directory:", err) return } g := NewGoLimit(10) // max_num(最大允许并发数)设置为10 strArray := []string{} var wg sync.WaitGroup fmt.Println("files_entry-len:", len(files_entry)) start := time.Now() for _, file_entry := range files_entry { g.Add() // 尝试增加一个协程,若已达到最大并发数,将阻塞 wg.Add(1) go func(file fs.DirEntry, g *GoLimit) { defer g.Done() defer wg.Done() // fmt.Println(goid.Get()) // 获取并打印 Goroutine id // 安装 goid 包:go get -u github.com/petermattis/goid content, err := os.ReadFile(path + "\\" + file.Name()) // 读取文件内容 if err != nil { fmt.Println("Error reading file:", err) return } mutex.Lock() // 加锁 strArray = append(strArray, string(content)) // 将文件内容加入数组 mutex.Unlock() // 解锁 time.Sleep(time.Millisecond * 10) // 模拟耗时操作 }(file_entry, g) } wg.Wait() end := time.Now() fmt.Println("strArray-len:", len(strArray)) fmt.Println("time.Sub(start):", end.Sub(start)) fmt.Println("end-------------------!") } type GoLimit struct { ch chan int } func NewGoLimit(max int) *GoLimit { return &GoLimit{ch: make(chan int, max)} } func (g *GoLimit) Add() { g.ch <- 1 } func (g *GoLimit) Done() { <-g.ch }
三、C# 语言实现
如下代码,基于 .NET 7.0,通过信号量 SemaphoreSlim 控制同时工作的线程数量:
// 必要的引用 using System; using System.IO; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using System.Diagnostics;
class Program { private static SemaphoreSlim semaphore; static void Main(string[] args) { ConcurrentBag<string> strings = new ConcurrentBag<string>(); int semaphorenum = 10; semaphore = new SemaphoreSlim(0, semaphorenum); // (初始数量,最大数量) string path = "E:\\OA日常文件\\test\\gotestfile"; var files = Directory.GetFiles(path); semaphore.Release(semaphorenum); // 由于初始数量为 0 所以需要手动释放信号量 Task[] tasks = new Task[1000]; Console.WriteLine($"总信号量:{semaphorenum}"); Console.WriteLine($"总文件数:{files.Length}"); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < files.Length; i++) { semaphore.Wait(); // 调用 Wait() 方法,标记等待进入信号量 string content = File.ReadAllText(files[i]); var task = Task.Run(() => { Thread.Sleep(10); // 模拟耗时操作 try { strings.Add(content); } finally { semaphore.Release(); // 调用 Release() 方法,释放信号量 } //Console.WriteLine(Thread.GetCurrentProcessorId()); }); tasks[i] = task; } Task.WaitAll(tasks); stopwatch.Stop(); Console.WriteLine($"信号量为 {semaphorenum} 时的耗时:{stopwatch.ElapsedMilliseconds}"); } }
四、执行结果比较
下表是运行后耗时情况对比:
同时运行的协程/线程数 | 2 | 5 | 10 | 50 | 100 | 500 | 1000 |
Go 耗时(ms) | 7740.52 | 3105.82 | 1546.60 | 308.87 | 168.50 | 144.24 | 126.00 |
C# 耗时(ms) | 7865 | 3199 | 1646 | 1275 | 1291 | 1236 | 1286 |
注:耗时统计均运行多次取稳定值。
由上图可得初步结论:
在分配的协程/线程数量较少时,两种语言的操作效率相似,随着协程/线程数量的增加,Go 较 C# 效率会明显提升。

本文来自博客园,作者:橙子家,欢迎微信扫码关注博主【橙子家czzj】,有任何疑问欢迎沟通,共同成长!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2023-03-27 SHA-256 简介及 C# 和 js 实现【加密知多少系列_5】