英文:
Are Golang goroutines (relatively) the same as C# tasks?
问题
我一直听说GO是自从切片面包以来最伟大的并发工具,但到目前为止我还没有看到太多证据。事实上,除了等待组和通道之外,我还没有找到任何好的同步构造。
据说goroutine应该非常轻量级和快速,因为它们不是实际的线程,而是GO使用的一种抽象。
这让我想起了C#的任务(Tasks)。
有没有人知道,从正在运行的数据或测试中,而不仅仅是口口相传,goroutine是否比C#的任务(Tasks)更快/更轻量级?
我正在试图找出如果我已经了解C#,是否有任何理由或好处使用GO。
英文:
I keep hearing how GO is the greatest thing since sliced bread for concurrency but so far I haven't seen much proof of that. In fact I haven't found any good synchronizing constructs besides wait groups and channels for synchronization.
Supposedly goroutines should be very lightweight and fast, since they are not an actual thread but an abstraction used by GO.
<br/>That made me think of C#'s Tasks.
Does anyone know, from data or tests being run, not just word of mouth, if goroutines are faster/lighter than C# tasks?
I'm trying to find out if there's any reason or benefit to use GO if I already know C#.
答案1
得分: 9
Go语言的goroutine和C#的任务(task)之间存在显著的差异,我认为它们并不等价。
共同特点
它们都是用户空间线程系统,能够进行M:N线程调度(在少量操作系统线程之上运行大量轻量级线程)。
差异
-
Goroutine可以被抢占。这意味着无论有多少计算密集型的goroutine,都不能阻塞其他工作的进行。
-
Goroutine可以在I/O操作期间持有锁。而在C#的任务中是不允许的。
-
你可以控制C#任务的调度方式,使用自己选择的调度器,自由地将不同的任务分配给不同的线程池,或者为任务分配优先级。你甚至可以将所有的C#任务都运行在单个线程上。
-
在goroutine中运行的代码只是普通的Go代码。而C#代码必须重写为使用async/await/Task。
在我看来,C#的任务只是将代码重写为状态机,并将状态作为堆上的对象而不是栈上的对象,这只是一种语法糖。所有的调度都是在现有线程原语之上的普通库代码中完成的。
Goroutine的设计更接近于操作系统线程的行为(锁、I/O和抢占),尽管它们并不完美。
如果你使用普通的线程原语来等待条件,很容易使你的C#任务出现死锁,但任务调度器没有剩余的线程可用来调度你正在等待的任务。这就是为什么在持有锁的情况下不能使用await...调度器可能会调度第二个任务,而第二个任务又在等待第一个任务持有的锁。
C#的做法的一个重要优点是你可以使用自己的调度器。我在单元测试中广泛使用了这个功能。相比之下,为并发的Go代码编写测试可能会更麻烦,而在C#中则非常容易。
英文:
There are significant differences between the behavior of Go goroutines and C# tasks that I would not consider them equivalent.
Common Features
They are both userspace threading systems capable of doing M:N threading (large number of lightweight threads on top of a small number of OS threads).
Differences
-
Goroutines can be preepmted. This means that no matter how many compute-bound goroutines you have, you can't block other work from progressing.
-
Goroutines can hold locks across I/O. This is disallowed in C# tasks.
-
You can control how C# tasks are scheduled, using a scheduler of your own choosing, freely assign different tasks to different pools of threads, or assign priorities to tasks. You can even run all your C# tasks on a single thread.
-
Code that runs in a goroutine is just ordinary Go code. C# code must be rewritten to use async / await / Task.
The way I see it, C# tasks are syntactic sugar for rewriting your code as a state machine and capturing state as objects on the heap rather than on the stack. All scheduling is done in ordinary library code on top of an existing thread primitive.
Goroutines are designed to be much closer to OS threads in terms of their behavior (locks, IO, and preemption) although they are not perfect.
It is easy enough to make your C# tasks deadlock if you use ordinary threading primitives to wait for a condition, but the task scheduler has no remaining threads available to schedule the task you are waiting for. That's why you can't await while a lock is held... the scheduler might schedule a second task which ends up waiting for the lock held by the first task.
The big benefit of the C# way of doing things is that you can use your own scheduler. I used this extensively when unit testing. It can be a bit more annoying to write tests for concurrent Go code compared to how easy it is in C#.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论