多线程那点事—Parallel.for

先看段代码:

for (int i = 0; i < 10; i++)
{
    Task.Factory.StartNew(()=>Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} ~ {i}"));
}

从代码上可以看出我们预期是打印1~10,但实际的打印结果是:


7 ~ 10
4 ~ 10
10 ~ 10
9 ~ 10
4 ~ 10
3 ~ 10
5 ~ 10
9 ~ 10
6 ~ 10
8 ~ 10

与预期的不一致,我们预期是打印数字1到10,但实际打印出来的是10次10。因为这几个lambda表达式中使用了同一个变量,并且这些匿名函数共享变量值。

再来看下面这段代码:


Action<int> displayNumber = n => Console.WriteLine(n);
int i = 5;
Task taskOne = Task.Factory.StartNew(() => displayNumber(i));
i = 7;
Task taskTwo = Task.Factory.StartNew(() => displayNumber(i));
Task.WaitAll(taskOne,taskTwo);

输出结果:


7
7

当闭包通过lambda表达式捕获可变变量时,lambda捕获变量的引用,而不是捕获该变量的当前值。因此,如果任务在变量的引用值更改后运行,则该值将是内存中最新的值,而不是捕获变量时的值。

为解决该问题,我们引入Parallel类来解决问题:

Parallel.For(0,10,i=>Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} ~ {i}"));

打印结果:


1 ~ 0
1 ~ 2
3 ~ 1
3 ~ 4
3 ~ 7
3 ~ 8
3 ~ 9
1 ~ 3
5 ~ 5
4 ~ 6

Parallel 类 提供对并行循环和区域的支持, 现在我们看下Parallel.for的代码:


// this needs to be in try-block because it can throw in  BuggyScheduler.MaxConcurrencyLevel
                rootTask = new ParallelForReplicatingTask(
                    parallelOptions,
                    delegate
                    {
                        //
                        // first thing we do upon enterying the task is to register  as a new "RangeWorker" with the
                        // shared RangeManager instance.
                        //
                        // If this call returns a RangeWorker struct which wraps the  state needed by this task
                        //
                        // We need to call FindNewWork32() on it to see whether  there's a chunk available.
                        //
                        // Cache some information about the current task
                        Task currentWorkerTask = Task.InternalCurrent;
                        bool bIsRootTask = (currentWorkerTask == rootTask);
                        RangeWorker currentWorker = new RangeWorker();
                        Object savedStateFromPreviousReplica =  currentWorkerTask.SavedStateFromPreviousReplica;
                        if (savedStateFromPreviousReplica is RangeWorker)
                            currentWorker =  (RangeWorker)savedStateFromPreviousReplica;
                        else
                            currentWorker = rangeManager.RegisterNewWorker();
                        // These are the local index values to be used in the  sequential loop.
                        // Their values filled in by FindNewWork32
                        int nFromInclusiveLocal;
                        int nToExclusiveLocal;
                        if (currentWorker.FindNewWork32(out nFromInclusiveLocal, out  nToExclusiveLocal) == false ||
                            sharedPStateFlags.ShouldExitLoop(nFromInclusiveLocal) ==  true)
                        {
                            return; // no need to run
                        }
                        // ETW event for ParallelFor Worker Fork
                        if (TplEtwProvider.Log.IsEnabled())
                        {
                            TplEtwProvider.Log.ParallelFork((currentWorkerTask != null  ? currentWorkerTask.m_taskScheduler.Id : TaskScheduler.Current.Id),  (currentWorkerTask != null ? currentWorkerTask.Id : 0),
                                                             forkJoinContextID);
                        }
                        TLocal localValue = default(TLocal);
                        bool bLocalValueInitialized = false; // Tracks whether  localInit ran without exceptions, so that we can skip localFinally if it wasn't
                        try
                        {
                            // Create a new state object that references the shared  "stopped" and "exceptional" flags
                            // If needed, it will contain a new instance of  thread-local state by invoking the selector.
                            ParallelLoopState32 state = null;
                            if (bodyWithState != null)
                            {
                                Contract.Assert(sharedPStateFlags != null);
                                state = new ParallelLoopState32(sharedPStateFlags);
                            }
                            else if (bodyWithLocal != null)
                            {
                                Contract.Assert(sharedPStateFlags != null);
                                state = new ParallelLoopState32(sharedPStateFlags);
                                if (localInit != null)
                                {
                                    localValue = localInit();
                                    bLocalValueInitialized = true;
                                }
                            }
                            // initialize a loop timer which will help us decide  whether we should exit early
                            LoopTimer loopTimer = new  LoopTimer(rootTask.ActiveChildCount);
                            // Now perform the loop itself.
                            do
                            {
                                if (body != null)
                                {
                                    for (int j = nFromInclusiveLocal;
                                         j < nToExclusiveLocal &&  (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE  // fast path  check as SEL() doesn't inline
                                                                   ||  !sharedPStateFlags.ShouldExitLoop()); // the no-arg version is used since we have  no state
                                         j += 1)
                                    {
                                        body(j);
                                    }
                                }
                                else if (bodyWithState != null)
                                {
                                    for (int j = nFromInclusiveLocal;
                                        j < nToExclusiveLocal &&  (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE  // fast path  check as SEL() doesn't inline
                                                                   ||  !sharedPStateFlags.ShouldExitLoop(j));
                                        j += 1)
                                    {
                                        state.CurrentIteration = j;
                                        bodyWithState(j, state);
                                    }
                                }
                                else
                                {
                                    for (int j = nFromInclusiveLocal;
                                        j < nToExclusiveLocal &&  (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE  // fast path  check as SEL() doesn't inline
                                                                   ||  !sharedPStateFlags.ShouldExitLoop(j));
                                        j += 1)
                                    {
                                        state.CurrentIteration = j;
                                        localValue = bodyWithLocal(j, state,  localValue);
                                    }
                                }
                                // Cooperative multitasking hack for AppDomain  fairness.
                                // Check if allowed loop time is exceeded, if so save  current state and return. The self replicating task logic
                                // will detect this, and queue up a replacement task.  Note that we don't do this on the root task.
                                if (!bIsRootTask && loopTimer.LimitExceeded())
                                {
                                    currentWorkerTask.SavedStateForNextReplica =  (object)currentWorker;
                                    break;
                                }
                            }
                            // Exit if we can't find new work, or if the loop was  stoppped.
                            while (currentWorker.FindNewWork32(out  nFromInclusiveLocal, out nToExclusiveLocal) &&
                                    ((sharedPStateFlags.LoopStateFlags ==  ParallelLoopStateFlags.PLS_NONE) ||
                                       !sharedPStateFlags.ShouldExitLoop(nFromInclusiveLocal)));
                        }
                        catch
                        {
                            // if we catch an exception in a worker, we signal the  other workers to exit the loop, and we rethrow
                            sharedPStateFlags.SetExceptional();
                            throw;
                        }
                        finally
                        {
                            // If a cleanup function was specified, call it.  Otherwise, if the type is
                            // IDisposable, we will invoke Dispose on behalf of the  user.
                            if (localFinally != null && bLocalValueInitialized)
                            {
                                localFinally(localValue);
                            }
                            // ETW event for ParallelFor Worker Join
                            if (TplEtwProvider.Log.IsEnabled())
                            {
                                TplEtwProvider.Log.ParallelJoin((currentWorkerTask !=  null ? currentWorkerTask.m_taskScheduler.Id : TaskScheduler.Current.Id),  (currentWorkerTask != null ? currentWorkerTask.Id : 0),
                                                                 forkJoinContextID);
                            }
                        }
                    },
                    creationOptions, internalOptions);
                rootTask.RunSynchronously(parallelOptions.EffectiveTaskScheduler);  // might throw TSE
                rootTask.Wait();
                // If we made a cancellation registration, we need to clean it up  now before observing the OCE
                // Otherwise we could be caught in the middle of a callback, and  observe PLS_STOPPED, but oce = null
                if (parallelOptions.CancellationToken.CanBeCanceled)
                {
                    ctr.Dispose();
                }
                // If we got through that with no exceptions, and we were canceled,  then
                // throw our cancellation exception
                if (oce != null) throw oce;

body对于迭代范围 (的每个值调用一次委托 fromInclusive , toExclusive) 。提供两个参数:

1、一个 Int32 值,该值表示迭代次数。

2、ParallelLoopState可用于提前中断循环的实例。ParallelLoopState对象是由编译器创建的; 它不能在用户代码中实例化。

继续来看:


Parallel.For(0, 10, (i,state) =>
            {
                if (i > 5)
                    state.Break();
                Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} ~ {i}");
            } );

输出:


1 ~ 0
1 ~ 1
1 ~ 2
1 ~ 3
1 ~ 4
1 ~ 5
1 ~ 6

在上面的方法中我们使用了 break方法。

调用 Break 方法会通知 for 操作,在当前的迭代之后,无需执行迭代。不过,如果所有迭代尚未执行,则仍必须执行当前的所有迭代。

因此,调用 Break 类似于 for c# 等语言中的传统循环内的中断操作,但它并不是完美的替代方法:例如,无法保证当前的迭代不会执行。

今天就先写道这里~
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Parallel.For 和 Parallel.ForEach 是 C# 中 System.Threading.Tasks 命名空间中的并行循环运算符。 Parallel.For 用于并行循环迭代固定数量的迭代次数。例如: ``` Parallel.For(0, 10, i => { Console.WriteLine("Iteration {0}", i); }); ``` Parallel.ForEach 用于并行循环迭代集合中的元素。例如: ``` List<string> words = new List<string> { "apple", "banana", "cherry" }; Parallel.ForEach(words, word => { Console.WriteLine("Word: {0}", word); }); ``` 这两种运算符都会在多个线程中并行运行,可以帮助提高程序的运行效率。 ### 回答2: Parallel.For和Parallel.ForEach是.NET Framework提供的用于并行处理迭代操作的两个方法。 首先,我们来看一下Parallel.For的例子。假设我们有一个包含100个数字的数组,我们想对每个数字进行平方操作,并在控制台输出。我们可以使用Parallel.For来实现并行化的迭代处理。代码如下: ``` int[] numbers = Enumerable.Range(1, 100).ToArray(); Parallel.For(0, numbers.Length, i => { numbers[i] = numbers[i] * numbers[i]; Console.WriteLine(numbers[i]); }); ``` 在这个例子中,我们使用Enumerable.Range方法创建了一个包含1到100的数组。然后,我们使用Parallel.For方法并行遍历数组中的每个元素。对于每个元素,我们将其平方,并在控制台输出结果。 接下来,我们来看一下Parallel.ForEach的例子。假设我们有一个字符串列表,我们想要并行地将每个字符串转换为大写,并将结果存储在新的列表中。我们可以使用Parallel.ForEach来实现并行化的迭代处理。代码如下: ``` List<string> strings = new List<string> { "hello", "world", "parallel", "foreach" }; List<string> upperCaseStrings = new List<string>(); Parallel.ForEach(strings, str => { upperCaseStrings.Add(str.ToUpper()); }); foreach (string str in upperCaseStrings) { Console.WriteLine(str); } ``` 在这个例子中,我们创建了一个包含几个字符串的列表。然后,我们使用Parallel.ForEach方法并行地遍历列表中的每个字符串。对于每个字符串,我们将其转换为大写,并将结果添加到一个新的列表中。最后,我们使用普通的foreach循环,在控制台输出转换为大写的字符串。 总结起来,Parallel.For和Parallel.ForEach是.NET Framework提供的用于并行处理迭代操作的两个方法。它们可以帮助我们以更高效的方式处理大规模的迭代任务,并发挥多核处理器的优势。 ### 回答3: Parallel.For 是 .NET Framework 中的一个多线程并行编程的方法,它允许我们以一种简单的方式创建一个 for 循环并在多个线程中并行执行循环中的迭代。 下面是一个使用 Parallel.For 方法的示例代码: ``` Parallel.For(0, 10, i => { Console.WriteLine("当前线程ID:{0},i的值为:{1}", Thread.CurrentThread.ManagedThreadId, i); }); ``` 上述代码会创建一个从0到9的循环,并使用多个线程并行地执行循环中的每个迭代。在控制台输出中,可以看到当前线程的ID和迭代的值。 而 Parallel.ForEach 方法也是 .NET Framework 中的一个多线程并行编程的方法,它允许我们以一种简单的方式遍历一个集合并在多个线程中并行执行每个元素的操作。 下面是一个使用 Parallel.ForEach 方法的示例代码: ``` List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; Parallel.ForEach(numbers, number => { Console.WriteLine("当前线程ID:{0},number的值为:{1}", Thread.CurrentThread.ManagedThreadId, number); }); ``` 上述代码会创建一个包含整数的 List 集合,并使用多个线程并行地执行每个元素的操作。在控制台输出中,可以看到当前线程的ID和每个元素的值。 总结来说,Parallel.For 和 Parallel.ForEach 提供了一种简单的方式在多个线程中并行执行循环或集合的操作。通过使用这些方法,我们可以更好地利用多核处理器和多线程编程,从而实现更高效的并行计算。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值