同步问题:一切似乎都是正确的,但是

时间:2009-09-04 01:39:52

标签: c# multithreading synchronization

我为.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;
                    }
                }
            }
        }
    }
}

正如您所看到的,线程一直等到集合为空,但似乎同步冲突禁止线程进入该点(整个代码中唯一的一个),其中一个元素从集合中移除{{ 1 {} list。 如果调用方法ContainerClass且值processList为0,则此冲突会导致代码永不返回,应用程序将继续运行。

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

非常感谢你的心底。

PS。我想强调一下,代码始终完全:从until_size调用thread.processList(0)时,它唯一可以用来制动它。

2 个答案:

答案 0 :(得分:1)

问题是您是否锁定ThreadClass对象本身而不是同步对象?

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

private static readonly object lockObject = new object()

并将lock(this)的所有来电替换为lock(lockObject)

MSDN明确建议不要做什么:

  

一般情况下,避免锁定公众   类型或代码之外的实例   控制。常见的构造锁定   (this),lock(typeof(MyType))和   锁(“myLock”)违反此规定   准则:

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

修改

我想我看到了一个僵局。如果在没有要处理的对象时调用run(),或者没有要处理的对象,则锁定(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()方法中的等待线程以退出锁定(this)区域,因此它们会彼此等待死锁。正确?

答案 1 :(得分:0)

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

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

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