1

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

例如认为有一个主线程 A 。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 Instance?单例只会在原始线程的范围内运行吗?

4

3 回答 3

0

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

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

在相关的说明中,大多数人认为 Singleton 是一个坏主意

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

于 2012-12-18T13:47:11.600 回答
0

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

让我们在您的问题中假设该类在类级别同步,然后当 A 在 S 上调用方法时,任何其他尝试同时调用 S 的线程必须等到 A 完成。

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

同时...A 可以在其他人访问 S 时继续运行 X(除非他们共享相同的锁)。

将锁——特别是本例中的互斥锁——视为一个令牌,只有持有该令牌的线程才能运行它所保护的代码。完成后,它会丢弃令牌,并且拾取它的下一个线程可以继续。

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

于 2012-12-18T13:49:39.817 回答
0

这将取决于您的单例或任何其他对象的线程安全程度。

例如,如果您使用Monitoror Mutex,则只有一个线程可以通过这些线程同步方法之一访问受保护的代码块。假设一个线程试图进入一个同步代码块,而另一个线程获得了锁:那么第二个线程将等待第一个线程释放锁。

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

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

检查此 MSDN 文章:

更新

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

明确地说:是的。任何线程,即使是主线程,都将被阻塞,直到获得锁的线程释放它

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

lock语句编译成类似 a Monitor.Enterand的东西Monitor.Exit。这意味着被锁定的对象获得了当前线程的排他锁。

更新 2

取自一些OP评论:

你能解释一下为什么吗?我的意思是,如果主线程此时对单例不做任何事情,那么线程不会尝试获取该锁?

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

当您使用线程同步方法(例如Monitorlock关键字在Monitor幕后使用 a ))保护代码区域时,您将阻塞任何试图进入受保护/同步对象的线程,而不是阻塞任何工作线程,直到Monitor释放 lock

假设有两个线程AB,并且您有以下代码:

lock(_syncObject)
{
    // Do some stuff
}

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

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

于 2012-12-18T13:55:02.807 回答