线程安全的单例运行在哪个上下文中?

时间:2012-12-18 13:34:45

标签: c# multithreading singleton

如果使用从多个线程访问的单例,并且单例本身是线程安全的,那么在访问单例时哪个线程会阻塞?

例如,认为有一个主线程A.首先访问单身人士S.然后做其他事情。

稍后线程B访问单例S.

如果B访问S,单例是否仍然在线程A的上下文中并且还阻塞线程A或仅阻塞线程B(以及其他试图实际访问它的线程?)

-> accesses
A->S {}
A->X {}
B->S {
...
C-S 
} - will B only block C or also A?

回答问题: 线程安全单例(剥离):

private static volatile Singleton instance;
    private static object _sync = new Object();

    private Singleton() {
        // dosomething...
    }

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (_sync)
                {
                    if (instance == null)
                        instance = new Singleton();
                }
            }

            return instance;
        }
    }

(并导致方法锁定)

问题主要针对以下几点: 我知道锁会阻止多个线程访问相同的代码区域。我的问题具体针对以下几点:

如果生成Singleton范围的原始线程不持有锁,如果另一个线程访问锁,它是否也会被阻塞,因为在范围内创建了Singleton实例?单例只会在原始线程的范围内运行吗?

3 个答案:

答案 0 :(得分:0)

通常,单身人士的线程安全意味着相互排斥。也就是说,如果一个线程需要使用单例,它必须获取一个锁/令牌,做它需要的东西,并释放令牌。在整个时间内它持有令牌,没有其他线程能够获取它。任何尝试的线程都将被阻塞并放入FIFO队列中,并在持有者释放后立即收到令牌。这确保了一次只有一个线程访问受保护资源(在这种情况下是单例对象)。

这是典型的情景;你的里程可能会有所不同。

相关说明,大多数人Singleton is considered a bad idea

由makc链接的教程的第2部分介绍了C#的同步机制,这非常好。

答案 1 :(得分:0)

线程安全通常意味着一次只能有一个线程访问它。关键部分周围的锁定将意味着多个线程试图运行该段代码将被阻止,并且一次只能有一个线程可以继续并访问它。

让我们在你的问题中假设类在类级别同步,然后当A在S上调用方法时,任何其他线程试图同时调用S 将不得不等到A完了。

一旦A完成运行S,那么所有等待的线程都可以重新调度,然后其中一个将获得锁并运行S(阻止任何剩余的等待线程)。

同时......当其他人正在访问S时,A可以继续运行X(除非他们共享同一个锁)。

考虑一个锁 - 特别是本例中的互斥锁 - 作为一个标记,只有持有该标记的线程才能运行它保护的代码。一旦完成它就会丢弃令牌,下一个拾取它的线程可以继续。

通常,您的同步是在比整个类更精细的级别上完成的,比如特定方法或方法中的特定代码块。这可以避免线程浪费时间等待,因为它们实际上都可以访问不会相互影响的不同方法。

答案 2 :(得分:0)

这取决于线程安全是你的单身人士还是其他任何对象。

例如,如果您使用MonitorMutex,则只有一个线程可以通过其中一种线程同步方法访问受保护的代码块。假设一个线程试图输入一个同步的代码块而另一个线程获得了锁定:然后第二个线程将等到第一个线程释放锁定。

另一方面,如果使用Semaphore,则可以定义有多少线程可以通过受保护的代码块。假设Semaphore同时允许8个线程。如果可能的第9个线程试图进入受保护的代码块,它将等到Semaphore通知可用于排队的线程的一个或多个插槽。

在使用多线程时,对于同步对象有不同的策略。

查看此MSDN文章:

更新

我已在您更新的问题正文中检查了您的代码。

明确:是的。任何线程,甚至主线程都将被阻塞,直到获得锁定的线程释放它

想象一下,这不会是这样的:一些线程规则适用于除主要线程之外的任何线程。这将是一个拥有非同步代码区域的机会。

lock语句会编译成Monitor.EnterMonitor.Exit之类的内容。这意味着锁定对象获取当前线程的独占锁。

更新2

取自一些OP评论:

  

你能解释一下原因吗?我的意思是如果主线程没有做任何事情   单身一刻,然后线程没有试图获得锁定?

哎呀!我觉得你忘记了线程是如何工作的!

当您使用Monitorlock关键字在场景后面使用Monitor)等线程同步方法保护代码区域时,您阻止任何尝试进入的线程受保护/已同步的对象,而不是阻止任何工作线程,直到Monitor释放锁

假设有两个帖子 A B ,你就有了这段代码:

lock(_syncObject)
{
    // Do some stuff
}

线程 A 通过同步代码区域而 B 是一个后台工作者,正在做一些其他不会通过受保护区域的东西。 在这种情况下, B 不会被阻止

换句话说:当您将线程访问同步到某个区域时,您正在保护对象。 lock(或MutexAutoResetEvent或其他)不等同于假设的Thread.SleepAll()。如果任何线程已启动且正常工作,并且没有人通过同步对象访问,则不会阻止任何线程。