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# 效率会明显提升。

  

相关推荐
Flynt15 小时前
Go 1.27 升了一波,泛型方法和 JSON v2 真香但有个坑
后端·go
用户1257585243617 小时前
对象存储 URL 为什么别到处拼:后台附件预览要验这一层
后端·go·ai编程
名字还没想好☜19 小时前
Go 的 database/sql 连接池实战:SetMaxOpenConns 怎么配、连接泄漏怎么查
数据库·sql·golang·go·数据库连接池
刚子编程19 小时前
用 Inno Setup 把 .NET 程序打包成安装包:从零到发布的完整指南
.net·inno setup·码农刚子·.net 程序打包·安装脚本编写
王中阳Go21 小时前
杰富瑞实测 8 款 AI Agent,国产千问 95 分登顶:我连夜把项目的 OpenAI 硬编码全拆了
人工智能·go
界面开发小八哥1 天前
界面控件DevExpress Blazor v26.1新版亮点 - TreeList & 数据编辑器
javascript·编辑器·.net·界面控件·blazor·devexpress·用户界面
程序员爱钓鱼1 天前
Go 编程实战:Map——使用 Key-Value 管理键值数据
后端·rust·go
rockey6271 天前
C#脚本引擎之AScript与Flee对比
c#·.net·script·eval·动态脚本
海盗12342 天前
微软技术周报 · 2026-08-11~2026-08-18
人工智能·microsoft·.net
Leinwin2 天前
微软MAI-Code-1.1-Flash技术解读:Token效率提升25%,CLI与.NET全面升级
microsoft·.net