Skip to main content
 首页 » 编程设计

c#之带有双重检查锁定的单例之错误 "being used by another process"

2024年01月26日25birdshome

我在网上阅读了很多关于 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 是如何解决这个问题的?

请您参考如下方法:

如评论中所述,您的问题是 WriteLog 根本不是线程安全的。

拥有单例并不意味着该类的每个方法都在同一个线程中。

即使你只有一个实例,它的方法仍然可以被多个线程同时调用。

解决方法:

在您的 WriteLog 方法中放置一些同步逻辑。 Lock 语句、ReaderWriterLockSlim...选择权在您手中!

如果您想了解有关 c# 中线程的更多信息,我推荐这篇文章:

http://www.albahari.com/threading/