我应该使用布尔变量来指示是否已获取锁定?

时间:2018-01-27 17:55:43

标签: c# multithreading

我目前正在构建一个多线程软件(在C#中),我不确定我的问题解决方案。

// isLocked is initialized at earlier stage
if (!isLocked)
{
   isLocked = true;
   // More code here
}

我知道条件检查是原子的,但我认为另一个线程可能会在' isLocked'之前进入if块。被分配了真实的'价值(因此造成不必要的情况)。

在Java中,我可以使用AtomicBoolean的方法' compareAndSet'这是原子的,但C#是等价的' CompareExchange'不是atmoic。

除了bool之外,我尝试使用lock,这样如果"已锁定"代码已经被执行,其他线程将绕过它。这是一个很好的方法吗,还是有更好的方法?

            Object myLock = new object();
            bool free = false;
            bool isLocked= actorsLocks[i];// Some Data structure 
            if (!isLocked)
            {
                lock(mylock)
                {
                    if (!isLocked)
                    {
                        isLocked= true;
                        free = true;
                    }
                }
            }
            if(free)
            {
                // actual method code here...
            }

是否有更有效的解决方案?

非常感谢你。

5 个答案:

答案 0 :(得分:1)

C#所需要的只是:

object theLock = new Object();

lock(theLock)
{
    // Lock is yours, enjoy
}

答案 1 :(得分:1)

  

在Java中,我可以使用AtomicBoolean的方法' compareAndSet'是的   原子,但 C#等价' CompareExchange'不是atmoic。

......呃......是的。否则它将完全没用。

https://msdn.microsoft.com/en-us/library/801kt583(v=vs.110).aspx#Anchor_2

  

如果comparand和location1中的值相等,则值为   存储在location1中。否则,不执行任何操作。 比较   和交换操作是作为原子操作执行的。   CompareExchange的返回值是location1中的原始值,   是否进行交换。

你的解决方案很接近。确保所有线程都可以访问您的锁定对象及其保护的所有变量。锁定本地声明的对象对你没有任何好处。例如,myLock可以是类数据成员。绝对不应该在函数中声明。同样适用于isLocked

class C
{
    int[] m_actorsLocks;  // *See below

    ...

    void WorkerFunction(int threadIndex)
    {       
        if (CompareExchange(ref m_actorsLocks[threadIndex], 1, 0) == 0)  // cmpxchg returns old value. If old value was false, it means WE locked it!
        {
            // do work

            // use cmpxchg to free the lock
            CompareExchange(ref m_actorsLocks[threadIndex], 0, 1)
            // I do this simply because I don't understand how C# caches outgoing writes
            // could possibly do m_actorsLocks[threadIndex] = 0; surrounded by Interlocked.MemoryBarrier()
        }
        else
        {
            // Threads who didn't get the lock come here...
            // If I understand what you're trying to do, you don't want the other threads to wait if they didn't get the lock.
            // So you probably wouldn't need this else clause...
        }
    }   

    ...

};

*如果将锁存储在数组中,您将会遇到错误的共享。由于数组是连续存储的,因此您的锁将位于同一个缓存行中...您将拥有与为所有内容使用1个锁相同的性能。处理这个的蹩脚方法是填充你的阵列。例如:

// If I want 4 locks 
int[] m_actorsLocks = new int[32];
// Now i only use index 0, 8, 16, and 24. The rest are just padding.

它有点混乱,需要知道你的架构...最好对这个进行一些研究,也许可以提出一个单独的问题。

答案 2 :(得分:0)

你是对的:bool的读取是原子的,但是几个线程可以原子地读取 bool并输入' if'在标志变为假之前阻止。 要做你想做的事情(一个线程只输入' if'块而不阻塞其他线程)你可以使用以下类型:

private long _n = 0;
......
if (Interlocked.Exchange(ref _n, 1) == 0)
{
    // More code here, only one thread at a time.
    // Be carefull with exceptions.
    Interlocked.Exchange(ref _n, 0); // Reset the flag for next running.
}

Interlocked.Exchange是一个原子读取和写入:它将阻止所有线程,除了将读取' _n = 0'写' _n = 1' 作为原子操作。在互锁的法规返回后,其他线程将立即获得1,并且它们不会进入块。

答案 3 :(得分:0)

如果您想同时尝试获取锁定并找出是否已获得锁定,请在一次原子操作中使用Monitor.TryEnter(object)

如果获取了锁,则

Monitor.TryEnter返回true,否则返回false。如果TryEnter返回true,则仅执行“锁定”代码。

public class SomeClassThatMultipleThreadsAccess
{
    private readonly object _lockObject = new object();

    public void MethodThatGetsCalledConcurrently()
    {
        if(Monitor.TryEnter(_lockObject))
        {
            try
            {
                // only one thread at a time can execute this in
                // one instance of the class.
                // If _lockObject is static then only one thread at
                // a time can execute this across all instances of
                // the class.
            }
            finally // very important - if we don't exit then nothing else can enter.
            {
                Monitor.Exit(_lockObject);
            }
        }
    }
}

请注意,用于锁定的对象不能在使用它的同一范围内声明。

这些都无济于事:

var lockObject = new object();
if(Monitor.TryEnter(lockObject))

var lockObject = new object();
lock(lockObject)
{

因为每个线程都会创建一个不同的对象,所以每个线程都会立即获取锁。它不会阻止对任何事情的并发访问。必须有一个对象,多个线程尝试获取锁。

答案 4 :(得分:0)

简而言之:你在寻找麻烦。别这么做。

更详细地说,有许多因素你低估了:

  1. CPU输送机优化。这意味着没有正确的"不要触摸,多个线程访问"标记您的CPU可以修改执行顺序。这可能会做出非常意想不到的事情,这些事情在单线程方面绝对合法,但可能会破坏你的同步逻辑。
  2. 编译器优化。这可以根据布尔值消除整个代码块(并且编译器不知道bool可以被另一个线程更改)。
  3. 虚假分享。这是更高级的事情,它无法改变您的程序行为。但是,它会导致显着的性能下降,因为您在连续数组中读取和编写手工制作的锁(这会破坏核心级缓存,最快速的缓存)。
  4. 这就是可以直接命名的东西。如果想得更多,我们可以找到更多针对手工线程同步机制的缺点。因此,多线程就像加密一样:不要试图重新发明它,你在危险的情况下知之甚少,并且会在几分钟甚至几秒钟内被破解。