编写高质量代码:改善c#程序的157个建议之“避免锁定不恰当的同步对象”

本文聚焦C#编程中的多线程同步问题,特别是避免锁定不恰当的同步对象。文中详细阐述了选择同步对象时应注意的五点:对象的可见性、静态变量的使用、值类型作为同步对象的限制、不推荐使用字符串作为同步对象以及降低同步对象的可见性。通过示例代码解释了错误做法可能导致的问题,并强调了线程同步的效率和正确性。此外,文章还提醒开发者避免使用`lock(this)`以及在非静态方法中将静态变量作为同步对象,指出值类型对象和字符串作为锁对象的局限性,并提倡隐藏同步对象以提高代码的安全性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

建议73:避免锁定不恰当的同步对象

在C#中,让线程同步的另一种编码方式就是使用线程锁。线程锁的原理,就是锁住一个资源,使得应用程序在此刻只有一个线程访问该资源。通俗地讲,就是让多线程变成单线程。在C#中,可以将被锁定的资源理解成new出来的普通CLR对象。

既然需要锁定的资源就是C#中的一个对象,我们就该仔细思考,到底什么样的对象能够成为一个锁对象(也叫同步对象)?在选择同步对象的时候,应当始终注意以下几点:

  1. 同步对象在需要同步的多个线程中是可见的同一个对象。
  2. 在非静态方法中,静态变量不应作为同步对象。
  3. 值类型对象不能作为同步对象。
  4. 避免将字符串作为同步对象。
  5. 降低同步对象的可见性。

下面分别详细介绍以上五个注意事项。

第一个注意事项:需要锁定的对象在多个线程中是可见的,而且是同一个对象。“可见的”这是显而易见的,如果对象不可见,就不能被锁定。“同一个对象”,这也很容易理解,如果锁定的不是同一个对象,那又如何来同步两个对象呢?虽然理解起来简单,但不见得我们在这上面就不会犯错误。为了帮助大家理解本建议的内容,我们先模拟一个必须使用到锁的场景:在遍历一个集合的过程中,同时在另外一个线程中删除集合中的某项。下面这个例子中,如果没有lock语句,将会抛出异常InvalidOperationException:“集合已修改;可能无法执行枚举”:
public partial class FormMain : Form
{
    public FormMain()
    {
        InitializeComponent();
    }

    AutoResetEvent autoSet = new AutoResetEvent(false);
    List<string> tempList = new List<string>() { "init0", "init1", "init2" };

    private void buttonStartThreads_Click(object sender, EventArgs e)
    {
        object syncObj = new object();

        Thread t1 = new Thread(() =>
        {
            //确保等待t2开始之后才运行下面的代码
            autoSet.WaitOne();
            lock (syncObj)
            {
                foreach (var item in tempList)
                {
                    Thread.Sleep(1000);
                }
            }
        });
        t1.IsBackground = true;
        t1.Start();

        Thread t2 = new Thread(() =>
        {
            //通知t1可以执行代码
            autoSet.Set();
            //沉睡1秒是为了确保删除操作在t1的迭代过程中
            Thread.Sleep(1000);
            lock (syncObj)
            {
                tempList.RemoveAt(1);
            }
        });
        t2.IsBackground = true;
        t2.Start();
    }
}
这是一个Winform窗体应用程序,需要演示的功能在按钮的单击事件中。对象syncObj对于线程t1和t2来说,在CLR中肯定是同一个对象。所以,上面的示例运行是没有问题的。

现在,我们将此示例重构。将实际的工作代码移到一个类型SampleClass中,该示例要在多个SampleClass实例间操作一个静态字段,如下所示:

private void buttonStartThreads_Click(object sender, EventArgs e)
{
    SampleClass sample1 = new SampleClass();
    SampleClass sample2 = new SampleClass();
    sample1.StartT1();
    sample2.StartT2();
}

class SampleClass
{
    public static List<string> TempList = new List<string>() { "init0",
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值