【问题标题】:Synchronization issues: everything seems correct, but同步问题:一切似乎都正确,但是
【发布时间】:2009-09-04 01:39:52
【问题描述】:

我为 .NET 编写了一个多线程应用程序,在非常重要的代码部分中,我有以下内容:

public class ContainerClass {
    private object list_lock;
    private ArrayList list;
    private object init_lock = new object();
    private ThreadClass thread;

    public void Start() {
        lock(init_lock) {
            if (thread == null) {
                thread = new ThreadClass();
                ...
            }
        }
    }

    public void Stop() {
        lock(init_lock) {
            if (thread != null) {
                thread.processList(0);
                thread.finish();
                thread.waitUntilFinished();
                thread = null;
            } else {
                throw new ApplicationException("Assertion failed - already stopped.");
            }

            ...
        }
    }

    private class ThreadedClass {
        private ContainerClass container;
        private Thread thread;
        private bool finished;
        private bool actually_finished;

        public ThreadedClass(ContainerClass container) {
            this.container = container;
            thread = new Thread(run);
            thread.IsBackground = true;
            thread.Start();
        }

        private void run() {
            bool local_finished = false;
            while (!local_finished) {
                ArrayList to_process = null;
                lock (container.list_lock) {
                    if (container.list.Count > 0) {
                        to_process = new ArrayList();
                        to_process.AddRange(container.list);
                    }
                }
                if (to_process == null) {
                    // Nothing to process so wait
                    lock (this) {
                        if (!finished) {
                            try {
                                Monitor.Wait(this);
                            } catch (ThreadInterruptedException) {
                            }
                        }
                    }
                } else if (to_process.Count > 0) {
                    // Something to process, so go ahead and process the journals,
                    int sz = to_process.Count;
                    // For all elements
                    for (int i = 0; i < sz; ++i) {
                        // Pick the lowest element to process
                        object obj = to_process[i];
                        try {
                            // process the element...
                            ...
                        } catch (IOException e) {
                            ...
                            // If there is an error processing the best thing to do is finish
                            lock (this) {
                                finished = true;
                            }
                        }
                    }
                }

                lock (this) {
                    local_finished = finished;
                    // Remove the elements that we have just processed.
                    if (to_process != null) {
                        lock (container.list_lock) {
                            int sz = to_process.Count;
                            for (int i = 0; i < sz; ++i) {
                                container.list.RemoveAt(0);
                            }
                        }
                    }
                    // Notify any threads waiting
                    Monitor.PulseAll(this);
                }
            }

            lock (this) {
                actually_finished = true;
                Monitor.PulseAll(this);
            }
        }

        public void waitUntilFinished() {
            lock (this) {
                try {
                    while (!actually_finished) {
                        Monitor.Wait(this);
                    }
                } catch (ThreadInterruptedException e) {
                    throw new ApplicationException("Interrupted: " + e.Message);
                }
            }
        }

        public void processList(int until_size) {
            lock (this) {
                Monitor.PulseAll(this);
                int sz;
                lock (container.list_lock) {
                    sz = container.list.Count;
                }
                // Wait until the sz is smaller than 'until_size'
                while (sz > until_size) {
                    try {
                        Monitor.Wait(this);
                    } catch (ThreadInterruptedException ) {
                    }
                    lock (container.list_lock) {
                        sz = container.list.Count;
                    }
                }
            }
        }
    }
}

如您所见,线程一直等到集合为空,但似乎同步冲突禁止线程进入从集合中删除元素的点(整个代码中唯一的一个)@987654322 @在ContainerClass。 如果使用 until_size 的值为 0 调用方法 processList,这种冲突会导致代码永远不会返回,并且应用程序会继续运行。

我请求任何比我更好的开发人员(我想那里有很多)帮助我修复这小段代码,因为我真的不明白为什么列表没有减少......

从心底里非常感谢你。

附言。我想强调代码始终完美工作:唯一会在从ContainerClass.Stop() 调用thread.processList(0) 时停止它的情况。

【问题讨论】:

  • 因为我不知道 StackOverflow 中的“社区 wiki”是什么意思,我认为这是一种获得更多可见性的方式......:P
  • 您不会因为回答而获得代表积分。查看页面顶部的常见问题解答。
  • 在调用 processList(int) 时代码是否也会死锁?或者,就在您通过 0 时?

标签: c# multithreading synchronization


【解决方案1】:

问题可能是您锁定了 ThreadClass 对象本身而不是同步对象吗?

尝试添加另一个私有变量来锁定:

private static readonly object lockObject = new object()

并将lock(this)的所有调用替换为lock(lockObject)

MSDN 明确反对你的行为:

一般来说,避免锁定公共 类型,或超出您的代码的实例 控制。常见的构造锁 (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反了这个 指导方针:

  lock (this) is a problem if the instance can be accessed publicly.

编辑:

我想我看到了死锁情况。如果在没有要处理的对象时调用 run(),或者没有要处理的对象,则 lock(this),然后调用 Monitor.Wait(this) 并且线程等待:

        if (to_process == null) {
            // Nothing to process so wait
            lock (this) { /* nothing's going to get this lock again until Monitor.PulseAll(this) is called from somewhere */
                if (!finished) {
                    try {
                        Monitor.Wait(this); /* thread is waiting for Pulse(this) or PulseAll(this) */
                    } catch (ThreadInterruptedException) {
                    }
                }
            }
        }

如果你在调用Container.Stop()时处于这种情况,当调用ThreadProcess.processList(int)时,你再次调用lock(this),因为run()方法还在有锁:

 lock (this) { /* run still holds this lock, waiting for PulseAll(this) to be called */
                Monitor.PulseAll(this); /* this isn't called so run() never continues */
                int sz;
                lock (container.list_lock) {
                    sz = container.list.Count;
                }

所以,不能调用 Monitor.PulseAll() 来释放 run() 方法中的等待线程以退出 lock(this) 区域,因此它们彼此处于死锁等待状态。对吧?

【讨论】:

  • 很抱歉,这个解决方案并没有改变这种情况......还是谢谢你的尝试...... :)
  • @Antonello,没问题。删除社区 wiki(单击编辑,取消选中社区 wiki)。
【解决方案2】:

我认为你应该尝试更好地解释你真正想要实现的目标。

 public void processList(int until_size) {
        lock (this) {
            Monitor.PulseAll(this);

这看起来很奇怪,因为您应该在更改锁定状态而不是从锁定开始时调用 Monitor.Pulse。 您在哪里创建工作线程 - 这部分不清楚,因为我只看到 Thread.Start()? 顺便说一句,我建议你看看PowerCollections - 也许你会在那里找到你需要的东西。

【讨论】:

  • 我更新了代码示例,将这些点包含在 ContainerClass 中,其中 ThreadClass 被实例化和调用。顺便说一句,方法“processList”通知所有线程在获得锁后立即等待访问锁定在“this”上的其他部分代码。
  • 您的说法无效。 PulseAll 的描述说“通知等待队列中的线程锁定对象的状态发生变化”。 - 但它只有在锁被释放时才会改变。
  • 我可能做了一些不连贯的事情(尽管我按照 Monitor.Pulse 的 MSDN 库中的示例:msdn.microsoft.com/en-us/library/…),但它有效,当我试图删除它时,一切都崩溃了。 .
  • 不是processList(0) 和waitUntilFinished 一样吗?我认为您正在使用状态的布尔变量杀死自己-相反,我建议您使用具有适当锁定功能的函数与更衣室对象。
猜你喜欢
  • 2022-01-23
  • 1970-01-01
  • 2022-10-06
  • 1970-01-01
  • 1970-01-01
  • 2014-06-14
  • 1970-01-01
  • 1970-01-01
  • 2019-04-09
相关资源
最近更新 更多