澳门在线威尼斯官方 > 澳门在线威尼斯官方 > 进程之中的三头,四线程之线程同步

原标题:进程之中的三头,四线程之线程同步

浏览次数:71 时间:2019-09-15

  在线程里,如果需要共享数据,那么一定需要使用同步技术,确保一次只有一个线程访问和改变共享数据的状态。在.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结构,也不要调用任何可能阻塞的内容。

一::lock语句

lock语句事设置锁定和接触锁定的一种简单方法。其语法非常简单:

            lock (obj)
            {
                // 需要发生同步的代码区
            }

将共享数据的操作代码,放在上述的“{...}”区域内。锁定的对象(obj)必须是引用类型,如果锁定一个值类型,实际是锁定了它的一个副本,并没有实现锁定功能。

一般地,被锁定对象需要被创建为 私有 只读 引用类型:

        private readonly object obj = new object();

二::Interlocked类

Interlocked类用于使变量的简单语句原子化。它提供了以线程安全的方式递增、递减、交换和读取值的方法。

        private int stateFlag = 0;

        public int IncrementState
        {
            //get
            //{
            //    lock (this)
            //    {
            //        stateFlag++;
            //        return stateFlag;
            //    }
            //}

            get
            {
                return Interlocked.Increment(ref stateFlag); // using System.Threading;

                //Interlocked.Decrement(ref V0);
                //Interlocked.Exchange(ref V1, ref V2);
                //Interlocked.Read(ref V0);
            }
        }

三::Monitor类

与lock相似,C#的lock语句被编译器解析为使用Monitor类。锁定开始相当于 Monitor.Enter(obj) 方法,该方法会一直等待,直到线程被对象锁定。解除锁定后线程进入同步阶段,使用 Monitor.Exit(obj)方法解除锁定,编译器将它与try块的finally结合。方法一中的代码,相当于:

            Monitor.Enter(obj);
            try
            {
                // 需要发生同步的代码区
            }
            finally
            {
                Monitor.Exit(obj);
            }

与lock语句相比,Monitor类的优点在于:可以添加一个等待北锁定的超时值。这样就不会无限期等待被锁定,而可以使用 TryEnter() 方法,给一个超时参数。

            bool lockTaken = false;
            Monitor.TryEnter(obj, 500, ref lockTaken);
            if (lockTaken)
            {
                try
                {
                    // acquired the lock
                    // synchronized region for obj
                }
                finally
                {
                    Monitor.Exit(obj);
                }
            }
            else
            {
                // didn't get the lock,do something else
            }

如果obj被锁定,TryEnter() 方法就会把 bool 型引用参数 lockTaken 设置为 true,并同步地访问由 obj 锁定的状态。如果另一线程 锁定 obj 的时间超过 500 毫秒,Try Enter() 方法就把变量 lockTaken 设为 false ,线程不再等待,而是用于执行其它操作。也许在之后,该线程会尝试再次被锁定。

 四::SpinLock结构

它是一个结构体(struct),用法极类似于Monitor类。获得锁用 Enter()或TryEnter() 方法,释放锁用 Exit() 方法。它还提供了属性 IsHeld 和 IsHeldByCurrentThred ,指定当前是否被锁定。

        SpinLock mSpinLock = new SpinLock(); // 最好只是用一个 SpinLock

        public void fun1()
        {
            // .....

            bool lockTaken = false;
            mSpinLock.Enter(ref lockTaken);
            try
            {
                // synchronized region
            }
            finally
            {
                mSpinLock.Exit();
            }

            // ...
        }
        public void fun2()
        {
            // .....

            bool lockTaken = false;
            mSpinLock.TryEnter(500, ref lockTaken);
            if (lockTaken)
            {
                try
                {
                    // synchronized region
                }
                finally
                {
                    mSpinLock.Exit();
                }
            }
            else
            {
                // didn't get the lock,do something else
            }

            // ...
        }

SpinLock结构体是 .Net 4 新增。它适用于:有大量的锁,且锁定时间都非常短。程序需要避免使用多个 SpinLock 结构,也不要调用任何可能阻塞的内容。

本文由澳门在线威尼斯官方发布于澳门在线威尼斯官方,转载请注明出处:进程之中的三头,四线程之线程同步

关键词:

上一篇:没有了

下一篇:没有了