【问题标题】:Thread safe progress counter线程安全进度计数器
【发布时间】:2021-09-19 13:48:16
【问题描述】:

我正在为我的任务使用Parallel 类,并且需要实现一个进度百分比计数器。尝试组合不同的锁,但要么性能很慢,要么结果如屏幕截图所示。

这是一个有效但速度慢的代码示例。因为我挡住了整个部分。

Parallel.ForEach(bossUsers, StandardParallelOptions(), (user) =>
{
    logger.Trace($"Sync new user {user.PID}.");
    while (queue.Count >= MaxDegreeOfParallelism)
    {
        Thread.Sleep(1);
    }

    queue.Enqueue(user);


    CreateOrUpdateUserAsync(
            cardRepository,
            dbScope,
            user,
            logger,
            cancellationToken)
        .ConfigureAwait(false);

    lock (locker)
    {
        var current = (int)(((progress) / (decimal)bossUsers.Count) * 100);
        if (current > 0 && current % 10 == 0)
        {
            if (last != current)
                logger.Info($"Progress: {current}%");
            last = current;
        }
    }

    logger.Trace($"Sync user {user.PID} complete.");
});

如果我移除锁,我会得到很多类似的行

请告知如何进行锁定,以免严重减慢服务速度。谢谢!

【问题讨论】:

  • 某事正在增加progress。为什么不做日志记录?
  • but either get very slow performance 不加锁有多快?带锁的速度有多快? 具体
  • 是的,通过 Interlocked.Increment(ref progress) 在 CreateOrUpdateUserAsync 方法中增加进度
  • 您不需要锁定。你知道集合的大小。你知道递增的进度。您可以根据当前进度指示器和指示器计算百分比 - 1。如果以前的值为 59%,而新值为 60%(即 10 的倍数),那么万岁 - 您需要记录。 不需要锁定
  • CreateOrUpdateUserAsync I/O 还是 CPU 受限?如果是前者,那么 Parallel.ForEach 不是你的朋友,因为它是为 CPU 绑定操作而设计的。如果您查看overloads,您会发现它们都没有预期到异步函数。在 .NET 6 中会有一个Parallel.ForeachAsync,它也可以用于 I/O 绑定操作。

标签: c# thread-safety parallel.foreach


【解决方案1】:

所以我最快的解决方案是只阻止变量“last”的赋值。我在没有显着性能损失的情况下达到了预期的结果。但仍然密切关注日志,我可以看到线程是如何变慢的。

我不阻止计算,如果检索余数,只分配和输出到日志。

var current = (int)((progress / (decimal)syncUsers.Count) * 100);

if (current > 0 && current % 10 == 0)
{
    lock (locker) // lock here only
    {
        if (last != current) logger.Info($"Complete: {current}%");
        last = current;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-27
    • 1970-01-01
    相关资源
    最近更新 更多