线程同步技术

参考:https://getpocket.com/a/read/929454653

一、先来看一个争用条件

SharedState类用于保存线程之间的恭喜那个数据,有一个成员State,不同的线程可以共享State。

    public class SharedState
    {
        public int State { get; set; }
    }

Job类包含DoTheJob()方法,该方法时新任务的入口点,通过代码实现,将sharedState类的State递增50000次,sharedState在这个类的构造函数中初始化(this.sharedState)

    public class Job
    {
        SharedState sharedState;
        public Job(SharedState sharedState)
        {
            this.sharedState = sharedState;
        }
        private object syncObj = new object();
        public void DoTheJob()
        {
            for (int i = 0; i < 50000; i++)
            {
         sharedState.State += 1;
            }
        } 
    }

在Main方法中,创建一个SharedState对象,并把它传递给20个Task对象的构造函数,在启动所有的任务后,Main()方法进入另一个循环,等待20个任务都执行完毕,把共享状态的值写入控制台中,因为执行了50000次循环,有20个任务,因此预期结果为1 000 000,但是事实并非如此。  

 

    class Program
    {
        static void Main(string[] args)
        {
            int numTasks = 20;
            var state = new SharedState();
            var tasks = new Task[numTasks];

            for (int i = 0; i < numTasks; i++)
            {
                tasks[i] = Task.Run(() => new Job(state).DoTheJob());
            }
            for (int i = 0; i < numTasks; i++)
            {
                tasks[i].Wait();

            }
            Console.WriteLine("summarized {0}", state.State);
            Console.ReadKey();
        }
    }

 执行三次的结果为:

summarized 381758

summarized 531860

summarized 316794

不同的机器执行结果可能不同,但是说明一个问题,在多线程并行执行的环境下,恭喜那个的数据有可能被其他线程修改而导致出现非预期结果。

比如共有两个线程t1和t2,这两个线程分别取state的值并给其加1,state初值为 1,当t1和t2从state取值的时候取出来的都是1,各自执行加1操作后将结果返回,那么我们得到的结果是2,而不是3,因为一个线程取出来的state值并不是另一个线程的执行结果,因此造成了结果错误。

二、C#用于多个线程同步的技术

如果需要在线程中共享数据,就需要使用同步技术,C#可以用于多线程同步的技术有:

  1. lock 语句
  2. Interlocked 类
  3. Monitor 类
  4. SpinLock 结构
  5. WaitHandle 类
  6. Mutex 类
  7. Semaphore 类
  8. Event 类
  9. Barrier 类
  10. ReaderWriterLockSlim 

1、lock语句

用lock语句定义的对象表示,要等待指定对象的锁定,只能传递引用类型。锁定类型只是锁定了一个副本,这其实没什么意义,如果对值类型使用了lock语句,C#编译器会发出一个错误。进行了锁定后--只锁定了一个线程,就可以运行lock语句块,在lock语句块的最后,对象的锁定被解锁,另一个等待锁定的线程就能获得该锁定块了。

我们尝试使用lock(this)和lock(obj) 来上锁。

将DoTheJob进行以下改造:

        public void DoTheJob()
        {
            lock(this)
            {
                for (int i = 0; i < 50000; i++)
                {
                   sharedState.State += 1;   
                }
            }
        } 

结果还是没有达到我们预期的100 000,这里的lock支队使用相同实例的线程起作用,tasks
[]中每个人物都调用不同的实例,所以它们都能同时使用DoTheJob方法。  

将DoTheJob进行以下改造:

        private object syncObj = new object();
        public void DoTheJob()
        {
            lock(syncObj)
            {
                for (int i = 0; i < 50000; i++)
                {
                   sharedState.State += 1;   
                }
            }
        }

运行结果也不正确。lock(syncObj)只会导致 DoTheJob() 不能被其他线程访问,但实例的其他成员依然可以被访问。

以下的例子可以更清楚的说明这一点。

lock(this)

    public class LockThis
    {
        private bool deadLock = true;
        public void DeadLocked()
        {
            lock(this)
            { 
                while(deadLock)
                {
                    Console.WriteLine("OMG! I am locked!");
                    Thread.Sleep(1000);
                }
                Console.WriteLine("DeadLocked() End");
             }
        }
        public void DontLockMe()
        {
            deadLock = false;
        }
        public  static void LockThisMethod()
        {
            LockThis lockThis = new LockThis();
            Task.Factory.StartNew(lockThis.DeadLocked);
            Thread.Sleep(5000);
            lock(lockThis)
            {
                lockThis.DontLockMe();
            }
        }
    }

  在Main()中调用LockThis.LockThisMethod即可调用此方法。

运行结果:

 

在LockThisMethod方法中,开始任务lockThis.DeadLocke

Task.Factory.StartNew(lockThis.DeadLocked);

企图任务开始5s以后通过LockThis.DontLockMe来解除死锁,但是并没有成功,LockThis.DeadLocked一直运行不停止。

因为死锁中lock(this)锁定了整个实例,导致外层想用同步方式访问此实例时,连非同步方法DontLockMe()也不能调用。

lock(syncObj)

    public class LockObject
    {
        private bool deadLock = true;
        private object syncObj = new Object();
        public void DeadLocked()
        {
            lock(syncObj)
            {
                while(deadLock)
                {
                    Console.WriteLine("OMG! I am locked!");
                    Thread.Sleep(1000);
                }
                Console.WriteLine("DeadLocked() End.");
            }
        }
        public void DontLockMe()
        {
            deadLock = false;
        }
        public static void LockObjectMethod()
        {
            LockObject lockObject = new LockObject();
            Task.Factory.StartNew(lockObject.DeadLocked);
            Thread.Sleep(5000);
            lock(lockObject)
            {
                lockObject.DontLockMe();
            }
        }
    }

 

  在Main()中调用LockObject.LockObjectMethod即可调用此方法。

运行结果:

 

在LockObjectMethod方法中,开始任务lockObject.DeadLocke

Task.Factory.StartNew(lockObject.DeadLocked);

企图任务开始5s以后通过LockThis.DontLockMe来解除死锁,成功了

因为死锁中lock(syncObj)只锁定了DeadLocked()方法,当外层也用同步方式访问该实例时,非同步方法DontLockMe可以被调用。

总结:因为类的对象也可以用于外部的同步访问( 上面的 lock(lockThis) 和 lock(lockObject) 就模拟了这种访问 ),而且我们不能在类自身中控制这种访问,所以应该尽量使用 lock(obj) 的方式,可以比较精确的控制需要同步的范围。 

lock(this)会锁定整个实例

lock(obj)只锁定范围内的代码。

lock(typeod(StaticClass))锁定静态成员

再来看一看,修改SharedState类可行不可行.

        public class SharedState
        {
            private object syncObj = new object();
            private int state=0;
            public int State 
            {
                get
                { lock (syncObj){return _state; } }
                set
                { lock (syncObj){state = value;}}
            }
        }

直接对共享状态控制同步,但是预期结果还是没有出来。

误区:对同步的理解错了,读和写之间,syncObj并没有被锁定,依然有线程可以在这个期间获得值。

解决问题的办法:  

1、将lock放到合适的地方,并采用合适的lock对象

        public void DoTheJob()
        {
                for (int i = 0; i < 50000; i++)
                {
                    lock(sharedState)
                    {
                        sharedState.State += 1;  
                    }
                }
        }

2、修改SharedState类的设计,作为一个原子操作提供递增方式

    public class SharedState
    {
        private int state = 0;
        private object syncRoot = new object();
        public int State
        {
            get { return state; }

        }
        public int incrementState()
        {
            lock(syncRoot)
            {
                return ++state;
            }
        }
    }

  

  

  

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。