【发布时间】:2009-10-01 15:30:50
【问题描述】:
我有多个线程在大致相同的时间启动 --- 所有线程都执行相同的代码路径。每个线程都需要将记录写入数据库中的表。如果表不存在,则应创建它。显然,两个或更多线程可以看到该表丢失,并尝试创建它。
确保此特定代码块仅由一个线程执行一次的首选方法是什么。
当我在 .NET 2.0 上使用 C# 编写代码时,我认为该方法是框架/语言中立的。
【问题讨论】:
标签: c# .net multithreading
我有多个线程在大致相同的时间启动 --- 所有线程都执行相同的代码路径。每个线程都需要将记录写入数据库中的表。如果表不存在,则应创建它。显然,两个或更多线程可以看到该表丢失,并尝试创建它。
确保此特定代码块仅由一个线程执行一次的首选方法是什么。
当我在 .NET 2.0 上使用 C# 编写代码时,我认为该方法是框架/语言中立的。
【问题讨论】:
标签: c# .net multithreading
这样的东西应该可以工作......
private object lockObject = new object();
private void CreateTableIfNotPresent()
{
lock(lockObject)
{
// check for table presence and create it if necessary,
// all inside this block
}
}
让您的线程调用调用CreateTableIfNotPresent 函数。 lock 块将确保没有线程能够同时执行块内的代码,因此当另一个线程正在创建表时,没有线程能够查看表不存在。
【讨论】:
lock 来解决你所有的并发问题吗?显然不是,或者像复数信号量和Mutex 类这样的项目不需要存在。对于这种性质的东西,lock 似乎非常适合。有一个单一的联系点(CreateTableIfNotPresent 函数),因此甚至不必担心必须确保您在其他地方lock 是正确的对象。说locks 是根本问题的标志是愚蠢的。
互斥锁确保一段特定的代码(或几段代码)一次只能由一个线程运行。您可以很聪明,为每个表使用不同的互斥锁,或者一次将整个初始化块限制为一个线程。
一个信号量(或一组信号量)可以执行完全相同的功能。
大多数锁实现将在内部使用互斥锁,因此请查看您正在使用的语言或库中已有哪些锁代码。
@ebpower 说得对,在某些应用程序中,您实际上会更有效地捕获因多次尝试创建同一个表而导致的异常,尽管在您的示例中可能并非如此。
但是,还有许多其他的处理方式。例如,您可以使用单线程 ExecutorService(抱歉,我只能找到 Java 参考)负责创建工作线程发现丢失的任何表。如果它收到对同一张表的两个请求,它会简单地忽略后面的请求。
Memoizer 的变体(记住表引用,必要时先创建它们)在这种情况下也可以工作。这本书Java Concurrency In Practice 介绍了一个不错的Memoizer class 的实现,但这很容易通过有效的并发构建块移植到任何其他语言。
【讨论】:
这就是Semaphores 的用途。
【讨论】:
您甚至可能不需要担心锁,因为您的数据库不应该让您创建多个同名的表。为什么不直接捕获适当的异常,如果两个线程尝试创建同一个表,一个获胜并继续,而另一个恢复并继续。
【讨论】:
我会使用诸如 ManualResetEvent 之类的线程同步对象,尽管在我看来您愿意使用竞态条件,这可能意味着您有设计问题
一些帖子建议使用互斥锁 - 除非您的线程在不同的进程上运行,否则这是一种矫枉过正的做法。
其他人建议使用锁——这很好,但锁会导致数据上的过度悲观锁,这可能首先否定使用线程的好处。
一个更根本的问题是你为什么要这样做?线程化对问题域有什么好处?并发能解决你的问题吗?
【讨论】:
您可能想尝试使用静态构造函数来获取表的引用。
根据MSDN (.net 2.0),静态构造函数用于初始化任何静态数据,或执行只需要执行一次的特定操作。
此外,CLR 自动保证静态构造函数每个 AppDomain 只执行一次,并且是线程安全的。
有关更多信息,请查看 Jeffrey Richter 撰写的 CLR 的第 8 章。
【讨论】: