并发系列64章(并行编程二)第六章
前言
续前一章。
1.并行调用
2.动态并行
3.并行linq
并行调用
指的是调用一批方法,并且这些方法是(大部分)相互独立的。
static void ProcessArray(double[] array)
{
Parallel.Invoke(
() => { ProcessPartialArray(array, 0, array.Length / 2); },
() => { ProcessPartialArray(array, array.Length / 2, array.Length); }
);
}
static void ProcessPartialArray(double[] array, int begin,int end)
{
// 开始计算
}
当然我们不知道到底有多少方法需要并行的时候,可以这样:
static void DoActionTimes(CancellationToken token, params Action[] action)
{
Parallel.Invoke(new ParallelOptions { CancellationToken=token} ,action);
}
之所以填写一个token进来,是希望有一个可以取消的状态。
动态并行
上面的并行调用中,解决了一个问题,就是在确定要并行的数量的时候,我们通过传入一个action数组,来实现。
但是呢,有时候我们不确定到底我们多少并行数量。上文中,我们action是一个数组已经确定了数量了,因为数组可以简单的遍历,通过下标就可以得到每一个并行委托。
有些却不能,如链表,树,图等,复制一些的数据,需要遍历计算的时候,就是在运行的时候才知道数量级是多少,所以又叫动态并行。
public class Node
{
public Node left;
public Node right;
}
static void Travrese(Node current)
{
// 对current 做一些操作
if (current.left!=null)
{
Task.Factory.StartNew(() => Travrese(current.left),
CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
}
if (current.right != null)
{
Task.Factory.StartNew(() => Travrese(current.right),
CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
}
}
static void ProcessTree(double[] array)
{
Node root = new Node();
var task= Task.Factory.StartNew(() => Travrese(root),
CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
task.Wait();
}
关键部分,在TaskCreationOptions.AttachedToParent;
//
// 摘要:
// 指定将任务附加到任务层次结构中的某个父级。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 System.Threading.Tasks.TaskContinuationOptions.AttachedToParent
// 选项以便将父任务和子任务同步。 请注意,如果使用 System.Threading.Tasks.TaskCreationOptions.DenyChildAttach
// 选项配置父任务,则子任务中的 System.Threading.Tasks.TaskCreationOptions.AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。
// 有关详细信息,请参阅附加和分离的子任务。
这种情况是父任务和子任务。
在此解释一下,task.wait() 因为父任务和子任务同步了,所以说这个await是等待了所以任务完成后,这里的并行是子任务是并行的,当然不能完全这么说,父父子子,子又是父,大概就是这个意思哈。
如果没有子任务等待你可以这样:
var task= Task.Factory.StartNew(() => { },
CancellationToken.None, TaskCreationOptions. None, TaskScheduler.Default);
task.ContinueWith(t => Trace.WriteLine("Task is dome"),
CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default
);
上面的code不是并行的范畴,而是异步编程的范畴了。
当然,上面的动态并行也可以这样写。
if (current.left!=null)
{
var leftTask=Task.Factory.StartNew(() => Travrese(current.left),
CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
}
if (current.right != null)
{
var rightTask=Task.Factory.StartNew(() => Travrese(current.right),
CancellationToken.None, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
}
Task.WaitAll(leftTask, rightTask);
当然了,上面肯定会报错,因为leftTask, rightTask拿不到。由此我们可见通过建立父子任务,这样在做一些判断的时候 current.right != null
来决定是否创建task的时候,waitall显得笨重,因为它必须确定task的数量。
并行linq
这个就是希望我们可以使用linq了,linq不仅仅是为了使我们的代码方便,而是一种编程思想,还很多好处,这里就不列举了,百度很多。
static IEnumerable<int> MultiplyBy2(IEnumerable<int> values)
{
return values.AsParallel().Select(item => item * 2);
}
这样是没有顺序的。
return values.AsParallel().AsOrdered().Select(item => item * 2);
这样是有顺序的,试想一下,如果让我们去实现并行后IEnumerable 按照原来的顺序,是不是非常麻烦。
总之,能使用linq就用linq吧。
下一章
整理了基础的数据流。