zoukankan      html  css  js  c++  java
  • 【C#】线程问题

        多线程编程对很多程序员来说并不容易,在启动访问相同数据的多个线程时,会间歇性地遇到难以发现的问题。如果使用任务、并行LINQ或Parallel类,也会遇到这些问题。为了避免这一系列问题,开发程序中必须注意同步问题和多个线程可能发生的其它问题。下面我们看一下争用条件和死锁。

    一、争用条件

      如果两个或多个线程访问相同的对象,或者访问不同步的共享状态(例如EF的实体),就会出现争用条件。为了说明争用条件,我们定义一个StateObject类,它包含一个int字段和一个ChangeState()方法。在该方法的实现代码中,验证状态变量是否包含5,。如果包含,就递增其值。然后用Trace.Assert方法来验证state是否包含6.在给包含5的变量递增了1后,可能希望变量的值是6,。但是事实却不一定是这样的。例如,如果一个线程刚刚执行完If(state==5)这一句代码,它就被其它线程调用,调度器运行另一个线程。第二个线程刚进入if语句,因为state的值仍是5,所以将它递增为6.现在,线程1再次被调度,那么结果state就变成了7,这时就发生的争用条件。

    public class StateObject
    {
        private int state=5;
        public void ChangeState(int loop){
            if(state==5){
                state++;
                Trace.Assert(state==6,"发生争用"+loop+"  loops");
            }
            state=5;
        }
    }

    下面通过给任务定义一个方法来验证这一点,SameTask类的RaceCondition()方法经一个StateObject类作为参数。在一个无限while循环中,调用其方法。变量i仅用于标示循环次数。

    public class SampleTask
    {
        public void RaceCondition(object o){
            Trace.Assert(o is StateObject,"o 必须是 StateObject类型");
            StateObject state=o as StateObject;
            int i=0;
            while(true){
                state.ChangeState(i++);
            }
        }
    }

    下面我们在Main方法中,新建一个StateObject对象,它由所有任务共享。我们看一下代码

    static void Main()
    {
        var state=new StateObject();
        for(int i=0;i<20;i++){
            Task.Factory.StartNew(new SampleTask().RaceCondition,state);
        }
        Thread.Sleep(10000);
    }

      

      运行程序,我们会看到错误提示,多次启动程序,会得到不同的结果,那么我们如何避免类似的问题呢,我们可以锁定共享的对象,这可以在线程中完成:用下面的lock语句锁定在线程中共享的state变量。只有一个线程能在锁定块中处理共享的对象。由于这个对象在所有的线程之间共享,因此如果一个线程锁定了改对象,那么其他线程就必须等待改锁的解除。一旦接受锁定,线程就拥有该锁定,直到改锁定块的你、末尾才解除锁定。

    public class SampleTask
    {
        public void RaceCondition(object o){
            Trace.Assert(o is StateObject,"o 必须是 StateObject类型");
            StateObject state=o as StateObject;
            int i=0;
            while(true){
                state.ChangeState(i++);
                lock(state)
                {
                    state.ChangeState(i++);
                }
            }
        }
    }

       在使用共享对象时,除了进行锁定外,还可以将共享对象设置为线程安全的对象。其中ChangeState()方法包含了lock语句,由于不能锁定state变量本身(只有引用类型才能用于锁定),因此定义一个object类型的变量,将它用于lock语句。

    public class StateObject
    {
        private int state=5;
        private object o=new object();
        public void ChangeState(int loop){
            lock(o){
                if(state==5){
                state++;
                Trace.Assert(state==6,"发生争用"+loop+"  loops");
            }
            state=5;
            }
        }
    }

    二、死锁
      过多的锁定也会有问题,在死锁中,至少有两个线程被挂起,并等待对象解除锁定。由于两个线程都在等待对方,就出现了死锁,那么后面线程将无限期等待下去。
    下面我们看一个死锁的例子,我们创建两个任务,

    var state1=new StateObject();
    var state2=new StateObject();
    Task.Factory.StartNew(new SampleTask(state1,state2).Deadlock1);
    Task.Factory.StartNew(new SampleTask(state1,state2).Deadlock1);
    
    public class SampleThread
    {
        private StateObject s1;
        private StateObject s2;
        public SampleThread(StateObject s1,StateObject s2)
        {
            this.s1=s1;
            this.s2=s2;
        }
        
        public void Deadlock1()
        {
            int i=0;
            while(true){
                lock(s1){
                    lock(s2){
                        s1.ChangeState(i);
                        s2.ChangeState(i++);
                        Console.WriteLine("{0}",i);
                    }
                }
            }
        }
        
        public void Deadlock2()
        {
            int i=0;
            while(true){
                lock(s2){
                    lock(s1){
                        s1.ChangeState(i);
                        s2.ChangeState(i++);
                        Console.WriteLine("{0}",i);
                    }
                }
            }
        }
    }

      Deadlock1()和DeadLock2()方法现在改变两个对象s1、s2的状态,这容易造成死锁,前一个方法先锁定s1,接着锁定s2,而两一个则相反,现在有可能前者s1的锁定被解除,出现一次线程切换,Deadlock2方法开始运行,并锁定s2,那么第二个线程现在等待s1锁定的解锁,因为它需要等待,所以线程调度器再次调度第一个线程,但第一个线程在等待s2的解锁,那么会造成两个线程都在等待,只要锁定块没有结束,就不会解锁,结果就是造成了死锁。

  • 相关阅读:
    help python(查看模块帮助文档)
    Vim常用快捷键
    tar 解压缩
    目前的学习计划
    学习方向
    C#转Python计划
    困惑的屌丝,求方向。。。
    修改PYTHONPATH的一种方法(在Window平台和Ubuntu下都有效)
    使用正则表达式统计vs项目代码总行数[转]
    日常工作细节汇总
  • 原文地址:https://www.cnblogs.com/wywnet/p/4469332.html
Copyright © 2011-2022 走看看