【问题标题】:C# - Lock not working but Volatile AND Lock does?C# - 锁不起作用但易失性和锁可以吗?
【发布时间】:2015-08-09 21:33:28
【问题描述】:

我正在尝试尽可能快速高效地轮询 API 以获取市场数据。 API 允许您根据请求从 batchSize 市场获取市场数据。 API 允许您有 3 个并发请求,但不能有更多(或引发错误)。

我可能会请求来自多个不同市场的batchSize 数据。

我不断循环遍历所有的市场,分批请求数据,每个线程一个批次,任何时候三个线程。

市场的总数(以及批次)可以随时变化。

我正在使用以下代码:

private static object lockObj = new object();

private void PollMarkets()
{
    const int NumberOfConcurrentRequests = 3;

    for (int i = 0; i < NumberOfConcurrentRequests; i++)
    {
        int batch = 0;
        Task.Factory.StartNew(async () =>
        {    
            while (true)
            {
                if (markets.Count > 0)
                {
                    List<string> batchMarketIds;

                    lock (lockObj)
                    {
                        var numBatches = (int)Math.Ceiling((double)markets.Count / batchSize);
                        batchMarketIds = markets.Keys.Skip(batch*batchSize).Take(batchSize).ToList();
                        batch = (batch + 1) % numBatches;
                    }

                    var marketData = await GetMarketData(batchMarketIds);

                    // Do something with marketData

                    }
                    else
                    {
                        await Task.Delay(1000); // wait for some markets to be added.
                    }
                }
            }
       });
    }
}

即使在临界区有锁,每个线程都以 batch = 0 开始(每个线程经常轮询重复数据)。

如果我将批处理更改为私有 volatile 字段,则上述代码将按我的意愿工作(易失性和锁定)。

所以由于某种原因我的锁不起作用?我觉得这很明显,但我错过了。

我认为这里最好使用锁而不是 volatile 字段,这也正确吗?

谢谢

【问题讨论】:

  • 请澄清您的问题。我不明白有什么不清楚的。你在多个线程之间共享一个变量,你说volatile 解决了你的问题。
  • @YuvalItzchakov volatile + lock 解决了问题,但我相信单独一个锁就足够了吗?
  • lockObj 是在哪里定义的?
  • @JamesBrierley 添加了 lockObj 定义
  • 在for循环外定义批处理是否有效?

标签: c# multithreading task-parallel-library task semaphore


【解决方案1】:

问题在于您在 for 循环中定义批处理变量。这意味着线程使用自己的变量而不是共享它。

【讨论】:

  • 为什么声明volatile 解决了这个问题呢? (或者可能没有,真的..)
  • @shay__ 必须将变量定义移动为易失性。您不能将局部变量声明为 volatile。
  • @JamesBrierley 抱歉,我不明白...如果真正的问题是每个线程都有自己的批处理变量,那么为什么将lockObj 声明为 volatile 解决了问题? lockObj 有什么关系?也许它并没有真正“解决”问题。
  • @shay__ 我将批处理变量声明为易失字段(方法外部)。
  • @Watson 哦,对不起...我以为是 lockObj 变量 :)
【解决方案2】:

在我看来,您应该使用 Queue 来创建作业管道。

类似的东西

private int batchSize = 10;
private Queue<int> queue = new Queue<int>();

private void AddMarket(params int[] marketIDs)
{
    lock (queue)
    {
        foreach (var marketID in marketIDs)
        {
            queue.Enqueue(marketID);
        }

        if (queue.Count >= batchSize)
        {
            Monitor.Pulse(queue);
        }
    }
}

private void Start()
{
    for (var tid = 0; tid < 3; tid++)
    {
        Task.Run(async () =>
        {
            while (true)
            {
                List<int> toProcess;

                lock (queue)
                {
                    if (queue.Count < batchSize)
                    {
                        Monitor.Wait(queue);
                        continue;
                    }

                    toProcess = new List<int>(batchSize);
                    for (var count = 0; count < batchSize; count++)
                    {
                        toProcess.Add(queue.Dequeue());
                    }

                    if (queue.Count >= batchSize)
                    {
                        Monitor.Pulse(queue);
                    }
                }

                var marketData = await GetMarketData(toProcess);
            }
        });
    }
}

【讨论】:

  • 感谢您的回答。如果我只需要获得每个市场一次(使用队列并在完成时弹出),这将是理想的。我必须分批迭代市场列表并轮询每个市场,直到市场关闭(然后从列表中删除)。
猜你喜欢
  • 2010-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多