【发布时间】:2014-02-13 23:25:56
【问题描述】:
我已经在网上阅读了很多关于 Singleton 的文章,但大多数文章只展示了创建简单的对象并正确锁定以确保线程安全而没有竞争条件。
简单到可以演示,我在里面放了一个文件记录方法。它适用于 10-20 个并发线程,但是当我尝试 100 个线程时,它将失败,给出“该进程无法访问该文件,因为它正被另一个进程使用”。
public class Logger
{
private string _path = "c:\\temp\\MyTestLog.txt";
// Singleton with padlock for DCL
private volatile static Logger _instance;
private static readonly object _padLock = new Object();
// Singleton
public static Logger Instance()
{
// Implement Double Check Locking (MSDN - Thread-safe)
if (_instance == null)
{
lock (_padLock)
{
if (_instance == null)
_instance = new Logger();
}
}
return _instance;
}
// Simple WriteLog
public void WriteLog(string text, out bool result)
{
try
{
// Write to file
using (Stream stream = File.Open(_path, FileMode.Append))
{
using (TextWriter writer = new StreamWriter(stream))
{
writer.WriteLine(text);
}
}
Debug.WriteLine(text);
result = true;
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
result = false;
}
}
}
为了从客户端调用它,我设置了一个简单的 Windows 窗体和线程以通过调用生成多线程:
Logger.Instance().WriteLog("test", out result);
好吧,我知道它失败的原因是因为前一个 _instance 被传递到下一个线程,其中内部文件操作未完成导致错误。但不知道如何解决这个问题。
我也试过这个..如果我用SqlConnection替换文件IO写入,我会得到“连接没有关闭。连接的当前状态是打开”而不是“进程无法访问文件,因为它正在被使用通过另一个过程”。同样的想法。当然,您将使用连接池而不是单例,但这只是出于我自己的好奇心。
我阅读了“深入了解 C#”的文章并尝试了不同的方法,但没有成功。即使使用 .NET 4.0 Lazy 也不能解决问题。唯一成功的解决方法是通过在 _instance 上使用 [ThreadStatic] 属性来打破单例规则,并且不再需要 DCL。但随后它将类变成了一个具有延迟初始化的多线程类,不再是真正的 Singleton。
所以,我想知道.. Singleton 是如何解决这个问题的?
【问题讨论】:
-
您提供的锁定机制确保只有一个
Logger实例存在(假设它是正确的)......但是您没有采取任何措施来阻止WriteLog中的 100 个线程在一次! -
@AakashM 是对的。您正在尝试同时写入 same file。 SQL 将创建一个新连接,您不会在那里共享同一个连接
-
问题不在于单例实现。问题出在
WriteLog方法中:多个线程可以访问它并同时打开/写入/关闭文件。您应该考虑重用相同的TextWriter实例并同步对该方法的访问。 -
还请记住,.NET 具有应用程序域的概念。如果您决定将此
Logger类放入实用程序DLL 中,然后从其他DLL 中调用它,那么单例模式会中断。在我们研究跨应用程序域单例以针对这种情况提供更强大的解决方案之前,我们对此感到厌烦。
标签: c# multithreading double-checked-locking