并发系列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吧。

下一章

整理了基础的数据流。

posted @ 2020-04-16 09:36  敖毛毛  阅读(156)  评论(0编辑  收藏  举报