Net4.0 Parallel编程(二)Data Parallelism 中_转
在上篇文章中看过了使用Parrallel.For、Parael.Foreach在效率上给我们带来的提高。本文就来如何终止循环、线程局部变量 进行说明。
Thread-Local Variables
首先我们来看下线程局部变量,是的我们也许一直在想我们如何去定义一个线程局部变量呢。先看段顺序执行的代码:
01.
[TestMethod()]
02.
public
void
NormalSequenceTest()
03.
{
04.
int
[] nums = Enumerable.Range(0, 1000000).ToArray();
05.
long
total = 0;
06.
for
(
int
i = 0; i < nums.Length;i++ )
07.
{
08.
total += nums[i];
09.
}
10.
Console.WriteLine(
"The total is {0}"
, total);
11.
}
执行结果:
我们再来看这段代码:
01.
[TestMethod()]
02.
public
void
NormalParallelTest()
03.
{
04.
int
[] nums = Enumerable.Range(0, 1000000).ToArray();
05.
long
total = 0;
06.
Parallel.For(0,nums.Length,i=>
07.
{
08.
total += nums[i];
09.
});
10.
Console.WriteLine(
"The total is {0}"
, total);
11.
}
执行结果:
再运行下:
也许我们会感到很奇怪为什么会这样呢,其实我们想想就可以明白了,total变量是公共的,而我们的程序是多个线程的加,而多个线程之间是不能把数据共享的。其实我们需要的是在每个线程中计算出一个和值,然后再进行累加。我们来看看线程局部变量:
01.
[TestMethod()]
02.
public
void
ThreadLocalTest()
03.
{
04.
int
[] nums = Enumerable.Range(0, 1000000).ToArray();
05.
long
total = 0;
06.
Parallel.For<
long
>(0, nums.Length, () => 0, (j, loop, subtotal) =>
07.
{
08.
subtotal += nums[j];
09.
return
subtotal;
10.
},
11.
(x) => Interlocked.Add(
ref
total, x)
12.
);
13.
14.
Console.WriteLine(
"The total is {0}"
, total);
15.
}
我们再看下执行结果:
下面说下泛型方法Parallel.For<T>方法,方法的原型:
1.
public
static
ParallelLoopResult For<TLocal>(
int
fromInclusive,
int
toExclusive, Func<TLocal> localInit, Func<
int
, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);
TLocal:线程变量的类型;第一个、第二个参数就不必多说了,就是其实值跟结束值。
localInit:每个线程的线程局部变量初始值的设置;
body:每次循环执行的方法,其中方法的最后一个参数就是线程局部变量;
localFinally:每个线程之后执行的方法。
相信这样解释后就能明白了为什么需要线程局部变量了,也明白如何使用线程局部变量了。我们再看看在Parallel.Foreach<T>中如何使用:
01.
[TestMethod()]
02.
public
void
ForeachThreadLocalTest()
03.
{
04.
int
[] nums = Enumerable.Range(0, 1000000).ToArray();
05.
long
total = 0;
06.
Parallel.ForEach<
int
,
long
>(nums,()=>0,(member,loopState,subTotal)=>
07.
{
08.
subTotal += member;
09.
return
subTotal;
10.
},
11.
(perLocal)=> Interlocked.Add(
ref
total,perLocal)
12.
);
13.
Console.WriteLine(
"The total is {0}"
, total);
14.
}
要注意的是,我们必须要使用ForEach<TSource, TLocal>,因为第一个参数表示的是迭代源的类型,第二个表示的是线程局部变量的类型,其方法的参数跟For是差不多的。
Break、Stop
首先我们可以看到在Parallel.For的一个重载方法中:
1.
public
static
ParallelLoopResult For(
int
fromInclusive,
int
toExclusive, Action<
int
, ParallelLoopState> body);
在委托的最后一个参数类型为ParallelLoopState,而ParallelLoopState里面提供给我们两个方法:Break、Stop来终止迭代,而Break跟Stop的区别是什么呢?我们来看两段代码:
01.
private
void
StopLoop()
02.
{
03.
var Stack =
new
ConcurrentStack<
string
>();
04.
Parallel.For(0, 10000, (i, loopState) =>
05.
{
06.
if
(i < 1000)
07.
Stack.Push(i.ToString());
08.
else
09.
{
10.
loopState.Stop();
11.
return
;
12.
}
13.
});
14.
Console.WriteLine(
"Stop Loop Info:\n elements count:{0}"
, Stack.Count);
15.
}
16.
private
void
BreakLoop()
17.
{
18.
var Stack =
new
ConcurrentStack<
string
>();
19.
var stringList =
this
.ConstructString(10000);
20.
Parallel.For(0, stringList.Count, (i, loopState) =>
21.
{
22.
Stack.Push(stringList[i]);
23.
if
(stringList[i].Contains(
"999"
))
24.
{
25.
loopState.Break();
26.
}
27.
});
28.
Console.WriteLine(
"Stop Loop Info:\n elements count:{0}"
, Stack.Count);
29.
}
30.
private
List<
string
> ConstructString(
int
number)
31.
{
32.
var stringList =
new
List<
string
>();
33.
Parallel.For(0, number - 1, i =>
34.
{
35.
stringList.Add(i.ToString());
36.
});
37.
return
stringList;
38.
}
测试方法:
1.
[TestMethod()]
2.
public
void
LoopTest()
3.
{
4.
StopLoop();
5.
BreakLoop();
6.
}
来看看运行结果吧:
其实这个例子只是想告诉大家,为什么第一个用Stop,第二个用Break。原因是:第一个例子中我只关心的是循环的迭代变量i的值,我只要1000个字符串,而不去管这1000个字符串是什么东西。所以当达到1000时,我们就立刻停止所有的迭代包括其他线程上的。而第二个方法中我们是判断的源中的某个索引值,这个时候有可能较早的元素还未处理。
其实在我们调用过Stop或者Break方法后,循环上的其他的线程可能还会运行一段时间,其实我们可以通过IsStopped属性来判断循环是在其他线程上停止。Foreach中的使用就不再看了,跟For是一样的。
总结
在本文中,主要介绍了如何停止循环、使用线程局部变量。在里面我们看到了我们在使用并行开发时,有很多东西是需要我们去注意的。在下文中将介绍下异常处理、取消循环等话题。