【问题标题】:Does my GetEnumerator cause a deadlock?我的 GetEnumerator 会导致死锁吗?
【发布时间】:2010-05-29 04:58:29
【问题描述】:

我开始编写我的第一个并行应用程序。此分区器将枚举 IDataReader 一次从数据源中提取 chunkSize 记录。

TLDR;版本

private object _Lock = new object();
public IEnumerator GetEnumerator()
{
    var infoSource = myInforSource.GetEnumerator();
                   //Will this cause a deadlock if two threads 
    lock (_Lock)   //use the enumator at the same time?
    {
        while (infoSource.MoveNext())
        {
            yield return infoSource.Current;
        }
    }
}

完整代码

protected class DataSourcePartitioner<object[]> : System.Collections.Concurrent.Partitioner<object[]>
{
    private readonly System.Data.IDataReader _Input;
    private readonly int _ChunkSize;
    public DataSourcePartitioner(System.Data.IDataReader input, int chunkSize = 10000)
        : base()
    {
        if (chunkSize < 1)
            throw new ArgumentOutOfRangeException("chunkSize");
        _Input = input;
        _ChunkSize = chunkSize;
    }

    public override bool SupportsDynamicPartitions { get { return true; } }

    public override IList<IEnumerator<object[]>> GetPartitions(int partitionCount)
    {

        var dynamicPartitions = GetDynamicPartitions();
        var partitions =
            new IEnumerator<object[]>[partitionCount];

        for (int i = 0; i < partitionCount; i++)
        {
            partitions[i] = dynamicPartitions.GetEnumerator();
        }
        return partitions;


    }

    public override IEnumerable<object[]> GetDynamicPartitions()
    {
        return new ListDynamicPartitions(_Input, _ChunkSize);
    }
    private class ListDynamicPartitions : IEnumerable<object[]>
    {
        private System.Data.IDataReader _Input;
        int _ChunkSize;
        private object _ChunkLock = new object();
        public ListDynamicPartitions(System.Data.IDataReader input, int chunkSize)
        {
            _Input = input;
            _ChunkSize = chunkSize;
        }

        public IEnumerator<object[]> GetEnumerator()
        {

            while (true)
            {
                List<object[]> chunk = new List<object[]>(_ChunkSize);
                lock(_Input)
                {
                    for (int i = 0; i < _ChunkSize; ++i)
                    {
                        if (!_Input.Read())
                            break;
                        var values = new object[_Input.FieldCount];
                        _Input.GetValues(values);
                        chunk.Add(values);
                    }
                    if (chunk.Count == 0)
                        yield break;
                }
                var chunkEnumerator = chunk.GetEnumerator();
                lock(_ChunkLock) //Will this cause a deadlock?
                {
                    while (chunkEnumerator.MoveNext())
                    {
                        yield return chunkEnumerator.Current;
                    }
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<object[]>)this).GetEnumerator();
        }
    }
}

我希望 IEnumerable 对象它传回是线程安全的(MSDN example 是所以我假设 PLINQ 和 TPL 可能需要它)锁定在底部附近的 _ChunkLock 是否有助于提供线程安全或会造成僵局?从文档中我无法判断锁定是否会在yeld return 上释放。

此外,如果 .net 有内置功能可以做我想做的事情,我更愿意使用它。如果您发现代码有任何其他问题,我将不胜感激。

【问题讨论】:

    标签: c# deadlock task-parallel-library result-partitioning


    【解决方案1】:

    一句话:也许*

    如果您总是在 foreach 循环的上下文中使用此代码,那么您不太可能遇到死锁(除非您的 myInfoSource 可能是无限的,或者你的foreach 循环中有一些永远不会终止的代码),尽管你可能会看到速度变慢。

    潜在的(实际上,有保证的)死锁更可能的原因是:

    var myObject = new YourObject();
    var enumerator = myObject.GetEnumerator();
    
    // if you do this, and then forget about it...
    enumerator.MoveNext();
    
    // ...your lock will never be released
    

    *我是根据您最初的代码块来回答这个问题的。

    【讨论】:

      【解决方案2】:

      我写了一个测试框架,它不会死锁但第二个线程永远不会得到数据。

      static void Main()
      {
          En en = new En();
          Task.Factory.StartNew(() =>
              {
                  foreach (int i in en)
                  {
                      Thread.Sleep(100);
                      Console.WriteLine("A:" + i.ToString());
                  }
              });
          Task.Factory.StartNew(() =>
          {
              foreach (int i in en)
              {
                  Thread.Sleep(10);
                  Console.WriteLine("B:" +i.ToString());
              }
          });
          Console.ReadLine();
      }
      
      public class En : IEnumerable
      {
          object _lock = new object();
          static int i = 0;
          public IEnumerator GetEnumerator()
          {
              lock (_lock)
              {
                  while (true)
                  {
                      if (i < 10)
                          yield return i++;
                      else
                          yield break;
                  }
              }
          }
      }
      

      返回

      A:0
      A:1
      A:2
      A:3
      A:4
      A:5
      A:6
      A:7
      A:8
      A:9
      

      这是GetEnumerator 的更新版本,应该可以正常运行。

      public IEnumerator<object[]> GetEnumerator()
      {
      
          while (true)
          {
              List<object[]> chunk = new List<object[]>(_ChunkSize);
              _ChunkPos = 0;
              lock(_Input)
              {
                  for (int i = 0; i < _ChunkSize; ++i)
                  {
                      if (!_Input.Read())
                          break;
                      var values = new object[_Input.FieldCount];
                      _Input.GetValues(values);
                      chunk.Add(values);
                  }
                  if (chunk.Count == 0)
                      yield break;
              }
              var chunkEnumerator = chunk.GetEnumerator();
              while (true)
              {
                  object[] retVal;
                  lock (_ChunkLock)
                  {
                      if (chunkEnumerator.MoveNext())
                      {
                          retVal = chunkEnumerator.Current;
                      }
                      else 
                          break; //break out of chunk while loop.
                  }
                  yield return retVal;
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-12
        相关资源
        最近更新 更多