当前位置:  开发笔记 > 编程语言 > 正文

锁定C#

如何解决《锁定C#》经验,为你挑选了2个好方法。

我还有点不清楚,什么时候可以锁定一些代码.我的一般经验法则是在读取或写入静态变量时将操作包装在锁中.但是当只读取静态变量时(例如,它是在类型初始化期间设置的只读),访问它不需要包含在锁定语句中,对吧?我最近看到一些代码看起来像下面的例子,它让我觉得我的多线程知识可能存在一些空白:

class Foo
{
    private static readonly string bar = "O_o";

    private bool TrySomething()
    {
        string bar;

        lock(Foo.objectToLockOn)
        {
            bar = Foo.bar;          
        }       

        // Do something with bar
    }
}

这对我来说没有意义 - 为什么会出现读取寄存器的并发问题?

此外,这个例子提出了另一个问题.其中一个比另一个好吗?(例如两个持有锁的时间较短?)我想我可以拆卸MSIL ......

class Foo
{
    private static string joke = "yo momma";

    private string GetJoke()
    {
        lock(Foo.objectToLockOn)
        {
            return Foo.joke;
        }
    }
}

class Foo
{
    private static string joke = "yo momma";

        private string GetJoke()
        {
            string joke;

            lock(Foo.objectToLockOn)
            {
                joke = Foo.joke;
            }

            return joke;
        }
}

Matt Howells.. 23

由于您编写的代码都没有在初始化后修改静态字段,因此不需要任何锁定.只是用新值替换字符串也不需要同步,除非新值取决于读取旧值的结果.

静态字段不是唯一需要同步的东西,任何可以修改的共享引用都容易受到同步问题的影响.

class Foo
{
    private int count = 0;
    public void TrySomething()    
    {
        count++;
    }
}

您可能会认为执行TrySomething方法的两个线程没问题.但事实并非如此.

    线程A将count(0)的值读入寄存器,以便递增.

    上下文切换!线程调度程序决定线程A有足够的执行时间.接下来是线程B.

    线程B将count(0)的值读入寄存器.

    线程B递增寄存器.

    线程B保存结果(1)以进行计数.

    上下文切换回A.

    线程A重新加载寄存器,其值为count(0)保存在其堆栈中.

    线程A递增寄存器.

    线程A将结果(1)保存到计数.

所以,即使我们两次调用count ++,count的值也只是从0变为1.让代码线程安全:

class Foo
{
    private int count = 0;
    private readonly object sync = new object();
    public void TrySomething()    
    {
        lock(sync)
            count++;
    }
}

现在,当线程A被中断时,线程B不会弄乱计数,因为它将触及锁定语句然后阻塞,直到线程A释放同步.

顺便说一下,有一种替代方法可以使Int32s和Int64s增加线程安全性:

class Foo
{
    private int count = 0;
    public void TrySomething()    
    {
        System.Threading.Interlocked.Increment(ref count);
    }
}

关于你问题的第二部分,我想我会选择哪个更容易阅读,任何性能差异都可以忽略不计.早期优化是万恶之源等.

为什么线程很难



1> Matt Howells..:

由于您编写的代码都没有在初始化后修改静态字段,因此不需要任何锁定.只是用新值替换字符串也不需要同步,除非新值取决于读取旧值的结果.

静态字段不是唯一需要同步的东西,任何可以修改的共享引用都容易受到同步问题的影响.

class Foo
{
    private int count = 0;
    public void TrySomething()    
    {
        count++;
    }
}

您可能会认为执行TrySomething方法的两个线程没问题.但事实并非如此.

    线程A将count(0)的值读入寄存器,以便递增.

    上下文切换!线程调度程序决定线程A有足够的执行时间.接下来是线程B.

    线程B将count(0)的值读入寄存器.

    线程B递增寄存器.

    线程B保存结果(1)以进行计数.

    上下文切换回A.

    线程A重新加载寄存器,其值为count(0)保存在其堆栈中.

    线程A递增寄存器.

    线程A将结果(1)保存到计数.

所以,即使我们两次调用count ++,count的值也只是从0变为1.让代码线程安全:

class Foo
{
    private int count = 0;
    private readonly object sync = new object();
    public void TrySomething()    
    {
        lock(sync)
            count++;
    }
}

现在,当线程A被中断时,线程B不会弄乱计数,因为它将触及锁定语句然后阻塞,直到线程A释放同步.

顺便说一下,有一种替代方法可以使Int32s和Int64s增加线程安全性:

class Foo
{
    private int count = 0;
    public void TrySomething()    
    {
        System.Threading.Interlocked.Increment(ref count);
    }
}

关于你问题的第二部分,我想我会选择哪个更容易阅读,任何性能差异都可以忽略不计.早期优化是万恶之源等.

为什么线程很难



2> Mark Bessey..:

读取或写入32位或更小的字段是C#中的原子操作.就我所见,您不需要锁定您提供的代码.

推荐阅读
echo7111436
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有