C# 中的 OpenMP 风格多线程






4.27/5 (10投票s)
简化 C# 中的 for 循环多线程,提供类似 OpenMP 的接口。
引言
本文演示了如何使用 C# 匿名委托和 using()
语句来实现一种非常简单的多线程接口,其方式类似于 C++ 中的 OpenMP。
同时,文章还提供了一个多线程对象 avForThread
的实现和示例。
背景
任何语言中的多线程都会在各个层面带来问题,从设计到实现再到调试。Visual C++ 的 OpenMP 扩展允许程序员快速并行化原本顺序执行的函数,或者至少测试更专业的线程例程是否对其任务有益。不幸的是,在撰写本文时,C# 还没有 OpenMP 扩展的实现!
然而,C# 确实拥有一些非常优雅的工具,可以将它们结合起来,产生一个与 OpenMP 非常相似的编程接口;主要是匿名委托和 using()
。
匿名委托有两个很好的特性。一,它们可以方便地在函数定义内部定义;二,在委托作用域之外但在定义作用域之内定义的变量仍然可见且可访问。
例如
public delegate void testSetter(int setTo);
void myMethod()
{
int b;
testSetter D = delegate(int setTo) { b= setTo; }
D(123);
}
在这种情况下,委托实例 D
被设置为一个内联定义的匿名委托。在这种情况下,委托将 b
设置为传入参数的值。
虽然这种语法在这个例子中显然没有用处,但它为程序员提供了很大的灵活性,尤其是在动态查询或无需增加新类的情况下实现多态行为时。
这种语法非常有用的一种情况是能够封装具有任意数量参数的函数,并让线程执行它们。
例如,如果我们把一个匿名委托传递给 System.Threading.Thread
对象的构造函数,我们就可以用我们选择的参数来运行这个委托。
所以,假设我们想把一个 for
循环分成多个线程来处理。(事实上,提供的代码只处理 for
循环,但可以轻松扩展到处理任何循环结构中的顺序互斥计算——只是不要写入可能被另一个线程读取或写入的数据!)由于我们想立即使用计算出的数据,我们将等待线程返回。
附加的代码在一个类 avForThread
中定义了这种操作所需的所有任务。
为了消除调用方法来等待线程完成的需要,我们可以利用 using()
语句的一个技巧,使得执行不会在 using
块结束前完成,直到线程返回。这是通过在 avForThread.Dispose()
中调用的线程等待方法 avForThread.WaitToFinish()
来实现的。
使用代码
因此,我们将首先创建一个测试 for
循环,这种代码是可以并行化的……
static void Main(string[] args)
{
// we want to call calculateSomething 1M times.
int dataSize = 1000000;
// we'll store the data in an array
int[] data = new int[dataSize];
// we'll do the same calculation twice,
// once without thread, and another time with
// without threading
for (int i = 0; i < dataSize; i++)
{
data[i] = calculateSomething(i);
}
然后,我们将使用一个 avForThread
对象,用多线程表示法执行相同的任务。我们需要传递与我们简单的 for
循环相同的开始和结束值。我们还将传递要使用的线程数,当然,还有要执行的参数化委托。
using (new avForThread(0, dataSize, 4,
delegate(int start, int end)
{
for (int i = start; i < end; i++)
{
data[i] = calculateSomething(i);
}
})
) ;
}
注意:要更改循环范围,我们可以修改 0
或 dataSize
参数。但是,我们不需要修改 start
或 end
——它们将由 avForThread
自动为每个线程设置。
如你所见,语法非常简单灵活。你需要尝试不同的线程数来获得最佳性能。根据任务的复杂度和 CPU,会出现一个“最佳点”,此时多线程代码比非多线程代码快得多。尝试将 numThreads
设置在 2 到 20 的范围内。
对于您自己的实现,您需要保持与此处 for
循环大致相同的格式。顺便说一下,这里的 using()
只是强制运行时调用类的 Dispose
方法,该方法反过来会等待线程完成。
您也可以省略 using
语句,继续工作,然后显式调用 avForThread.WaitToFinish()
方法。
正如我所说,如果您想超越简单的 i++ for
循环,那么需要对 avForThread
进行一些简单的自定义。
玩得开心!