zoukankan      html  css  js  c++  java
  • 进程内部的同步

      在线程里,如果需要共享数据,那么一定需要使用同步技术,确保一次只有一个线程访问和改变共享数据的状态。在.net中,lock语句、Interlocked类和Monitor类可用于进程内部的同步。

    1、lock语句与线程安全

      lock语句是设置锁定和解除锁定的一种简单方式。在使用lock语句之前,先进入另一个争用条件。例如:

    public class SharedState
    {
        public int State { get; set; }
    }
    public class Job
    {
        SharedState sharedState;
        public Job(SharedState sharedState)
        {
            this.sharedState = sharedState;
        }
        public void DoTheJob()
        {
            for (int i = 0; i < 50000; i++)
            {
                    sharedState.State += 1;
            }
        }
    }
    static void Main()
    {
        int numTasks = 20;
        var state = new SharedState();
        var tasks = new Task[numTasks];//定义20个任务
    
        for (int i = 0; i < numTasks; i++)
        {
            tasks[i] = Task.Run(() => new Job(state).DoTheJob());//启动20个任务,同时对数据进行修改
        }
    
        for (int i = 0; i < numTasks; i++)
        {
            tasks[i].Wait();//等待所有任务结束
        }
    
        Console.WriteLine("summarized {0}", state.State);//预想应该输出:summarized 1000000
    }

      实际上的输出与预想输出并不一致,每次运行的输出结果都不同,但没有一个是正确的。如果将线程数量减少,那么得到正确值的次数会增多,但也不是每次都正确。

      使用lock关键字,可以实现多个线程访问同一个数据时的同步问题。lock语句表示等待指定对象的锁定,该对象只能时引用类型。进行锁定后——只锁定了一个线程,就运行lock语句块中的代码,在lock块最后接触锁定,以便另一个线程可以锁定该对象。

    lock(obj)
    {
        //执行代码
    }
    //锁定静态成员,可以所以其类型(object)
    lock(typeof(StaticCalss))
    {
        //执行代码
    }

      所以修改以上的代码,使用SyncRoot模式。但是,如果是对属性的访问进行锁定:

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

      仍会出现前面的争用情况。在方法调用get存储器,以获得state的当前值,然后set存储器给state设置新值。在调用对象的get和set存储器期间,对象并没有锁定,另一个线程仍然可以获得临时值。最好的方法是在不改变SharedState类的前提下,在调用方法中,将lock语句添加到合适的地方:

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

      在一个地方使用lock语句并不意味着访问对象的其他线程都在等待。必须对每个访问共享数据的线程显示使用同步功能。

      为使对state的修改作为一个原子操作,修改代码:

    public class SharedState
    {
        private int state = 0;
        public int State { get { return state; } }
        public int IncrementState()
        {
            lock (this)
            {
                return ++state;
            }
        }
    }
    //外部访问
    public void DoTheJob()
    {
        for (int i = 0; i < 50000; i++)
        {
             sharedState.IncrementState();        
        }
    }

    2、Interlocked类

      Interlocked类用于使变量的简单语句原子化。i++并非线程安全的,它涉及三个步骤:取值、自增、存值。这些操作可能被线程调度器打断。Interlocked类提供了以线程安全的方式递增、递减、交换和读取值的方法。Interlocked类只能用于简单的同步问题,而且很快。因此,上面的IncrementState()方法的代码可以改为:return Interlocked.Increment(ref state);

    3、Monitor类

      lcok语句最终会有C#编译器解析为使用Monitor类。

    lock(obj)
    {
        //执行代码
    }

      简单的lock(obj)语句会被解析为调用Enter()方法,该方法会一直等待,直到线程锁定对象。一次只有一个线程能锁定对象,只要解除锁定,线程就可以进入同步阶段。Monitor类的Exit()方法解除锁定。编译器把Exit()方法放在try块的finally中,不论是否抛出异常,都将在语句块运行末尾解除锁定。

    Monitor.Enter(obj);
    try
    {
        //执行代码
    }
    finally
    {
        Monitor.Exit(obj);
    }

      相对于lock语句,Mpnitor类可以设置一个等待被锁定的超时值。这样就不会无限期的等待锁定,如果等待锁定时间超过规定时间,则返回false,表示未被锁定,线程不再等待,执行其他操作。也许以后,该线程会再次尝试获得锁定:

    bool lockTaken = false;
    Monitor.TryEnter(obj,500, ref lockTaken);//在500ms内,是否锁定了对象
    if (lockTaken)
    {
        try
        {
            //执行代码
        }
        finally
        {
            Monitor.Exit(obj);
        }
    }
    else
    {
        //未获得锁定,执行代码
    }

       如果基于对象的锁定对象(Monitor)的系统开销由于垃圾回收而过高,可以使用SpinLock结构。,SpinLock结构适用于:有大量的锁定,而且锁定时间总是非常短的情况。应避免使用多个SpinLock结构,也不要调用任何可能阻塞的内容。

  • 相关阅读:
    MCU软件最佳实践——独立按键
    MCU软件最佳实践——矩阵键盘驱动
    MCU软件最佳实践——使用printf打印数据
    CAP定理图解证明
    类型和变量
    数字ID过长 精度丢失 (已解决:后端方案)
    Springboot 异步线程池配置(小型应用)
    Java 数字 字符串 简单操作
    Java 网络请求
    Java 时间 日 周 月 季 年
  • 原文地址:https://www.cnblogs.com/pilgrim/p/9249981.html
Copyright © 2011-2022 走看看