代码改变世界

C# 线程手册 第三章 使用线程 AutoResetEvent, Mutex & Interlocked

2012-02-10 21:13  DanielWise  阅读(3539)  评论(6编辑  收藏  举报

AutoResetEvent 类

AutoResetEvent类的工作方式与ManualResetEvent类似。它会等超时事件发生或者信号事件发生然后通知正在等待的线程。ManualResetEvent和AutoResetEvent之间最重要差别之一是AutoResetEvent在WaitOne()方法执行完会改变自身状态。下面列表显示了如何使用AutoResetEvent类:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:oDaniel Dong
 * Blog:o  www.cnblogs.com/danielWise
 * Email:o guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace AutoReset
{
    class Auto
    {
        [STAThread]
        static void Main()
        {
            AutoResetEvent aRE = new AutoResetEvent(true);
            Console.WriteLine("Before First WaitOne");
            bool state = aRE.WaitOne(1000, true);
            Console.WriteLine("After First WaitOne " + state);
            state = aRE.WaitOne(5000, true);
            Console.WriteLine("After Second WaitOne " + state);
            Console.ReadLine();
        }
    }
}

AutoReset的输出结果与上一篇的ManualReset例子一样:

2012-2-10 16-25-24

通过AutoReset例子,AutoResetEvent和ManualResetEvent的差别很清晰了。事件对象状态在第一个WaitOne()函数由signaled变成non-signaled, 然后在第二个WaitOne()函数由non-signaled变成signaled. 结果是线程不会在第一个WaitOne()方法等待而不得不在第二个WaitOne()方法等待直到超时退出。

Mutex 类

Mutex, ManualResetEvent和AutoResetEvent类都继承自WaitHandle类。它与Monitor非常类似除了它可以用于进程间同步而后者不可以。我们来看一个例子,WroxMutex.cs:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:oDaniel Dong
 * Blog:o  www.cnblogs.com/danielWise
 * Email:o guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace WroxMutex
{
    class NETMutex
    {
        static Mutex myMutex;

        public static void Main()
        {
            myMutex = new Mutex(true, "WROX");
            NETMutex nm = new NETMutex();
            Thread t = new Thread(new ThreadStart(nm.Run));
            t.Start();
            Console.WriteLine("Thread Sleep for 5 sec");
            Thread.Sleep(5000);
            Console.WriteLine("Thread Woke Up");
            myMutex.ReleaseMutex();
            Console.WriteLine("Before WaitOne");
            myMutex.WaitOne();
            Console.WriteLine("Lock owned by Main Thread");
            Console.ReadLine();
        }

        public void Run()
        {
            try
            {
                myMutex.WaitOne();
                Console.WriteLine("Thread Sleep for 10 sec");
                Thread.Sleep(10000);
            }
            catch (AbandonedMutexException ex)
            {
                Console.WriteLine("Exception on return from WaitOne." +
                    "\r\n\tMessage: {0}", ex.Message);
            }
            finally
            {
                // Whether or not the exception was thrown, the current
                // thread owns the mutex, and must release it.
                //
                myMutex.ReleaseMutex();
            }
            Console.WriteLine("End of Run() method");
        }
    }
}

WroxMutex输出结果如下:

2012-2-10 17-09-41

在WroxMutex中,我们使用一个布尔值和一个字符串来作为Mutex构造函数的参数,它们分别表示调用线程应该有Mutex的最初所有权以及Mutex的名字。我们然后创建一个线程用来调用Run()方法。Mutex仍然属于主线程。在Run()方法中,线程t 不得不等待主线程释放Mutex锁。因此,线程t 会在Run()方法中对WaitOne()函数调用处等待。在睡眠5秒后,主线程释放Mutex锁。线程t获得Mutex锁的所有权并继续睡眠。现在,Main()方法无法获得Mutex 锁直到线程t释放它或者线程t退出。在这种情况下,线程t超时并退出,所以Mutex锁又重新被主线程获取。

Interlocked 类

Interlocked 可以用来对多线程共享访问的一个整型变量进行同步访问控制。这个操作在一个原子操作中进行。我们来看一个例子, WroxInterlocked.cs:

/*************************************
/* copyright (c) 2012 daniel dong
 * 
 * author:daniel dong
 * blog:  www.cnblogs.com/danielwise
 * email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace WroxInterlocked
{
    class WinterLocked
    {
        public ManualResetEvent a = new ManualResetEvent(false);
        private int i = 5;

        public void Run(object s)
        {
            Interlocked.Increment(ref i);
            Console.WriteLine("Thread ID = {0} Count = {1}", 
                Thread.CurrentThread.GetHashCode(), i);
        }
    }

    public class MainApp
    {
        public static void Main()
        {
            ManualResetEvent mR = new ManualResetEvent(false);
            WinterLocked wL = new WinterLocked();
            for (int i = 1; i <= 10; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(wL.Run), 1);
            }
            mR.WaitOne(10000, true);
            Console.ReadLine();
        }
    }
}

WroxInterlocked 的输出结果如下:

Interlocked

WroxInterlocked 显示了如何使用Interlocked类。我们在一个原子操作中增加全局变量i 的值。对应Increment()方法,也有一个Decrement()方法对变量值进行递减。Exchange()方法也按照同样行为更改作为ByRef 参数传递给它的两个变量值。

静态变量和静态方法的同步

同步锁对静态变量和静态方法的处理方法与实例变量和实例方法不同。静态变量是类变量,而属于一个对象的变量是对象变量或者实例变量。换句话说,一个类对象只有一个静态变量和静态方法实例而一个类对象的每个实例都可以有多个实例变量和实例方法。所以,如果你同步一个静态变量或者一个静态方法,那么锁就会应用到整个类上(或者说类类对象的所有实例上,这也是为什么我们经常定义一个私有静态对象作为锁)。结果是其他对象都不被允许访问类的静态变量。

ThreadStaticAttribute 类

ThreadStaticAttribute 用于静态变量,可以为每个访问静态变量的线程创建一个单独的静态变量拷贝,而不是在不同线程间共享静态变量(默认行为)。这意味着应用了ThreadStaticAttribute 的静态变量不会在访问它的线程间共享。每个访问它的线程都会拥有一个单独的拷贝。如果一个线程修改了变量值,其他线程访问同一个变量的时候不会看到改动后的值。这个行为与静态变量的默认行为相悖。简而言之,ThreadStaticAttribute 给了我们一个两全其美的方案(静态和实例)。

下面列表显示了ThreadStaticAttribute的使用, WroxShared.cs:

/*************************************
/* copyright (c) 2012 daniel dong
 * 
 * author:daniel dong
 * blog:  www.cnblogs.com/danielwise
 * email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace WroxStatic
{
    class ThreadStatic
    {
        [System.ThreadStaticAttribute()]
        public static int x = 1;
        public static int y = 1;

        public void Run()
        {
            for (int i = 1; i <= 10; i++)
            {
                Thread t2 = Thread.CurrentThread;
                x++; 
                y++;
                Console.WriteLine("i = " + i
                    +" ThreadID = " + t2.GetHashCode()
                    +" x(static attribute) = " + x
                    + " y = " + y);
                Thread.Sleep(1000);
            }
        }
    }

    public class MainApp
    {
        public static void Main()
        {
            ThreadStatic tS = new ThreadStatic();
            Thread t1 = new Thread(new ThreadStart(tS.Run));
            Thread t2 = new Thread(new ThreadStart(tS.Run));
            t1.Start();
            t2.Start();
            Console.ReadLine();
        }
    }
}

WroxShared.cs 的输出结果如下:

ThreadStaticAttribute

应用了ThreadStaticAttribute的一个静态变量和一个实例变量的差别是静态变量不需要生成一个对象实例来访问它,而如果你不创建一个对象实例就访问其实例变量,编译时就会报错。简而言之,再一次的,应用了ThreadStaticAttribute的静态变量不用生成新实例就可以访问且每个访问它的线程都有自己的拷贝,不会共享此静态变量。

同步和性能

同步为我们的计算体验带来质的飞跃的同时也带来了获得同步锁的时间开销。结果是其性能总是比那么非线程安全的版本要低。由于多个线程可能会在同一时间访问对象来获得同步锁,整体应用程序的性能可能在不经意间被影响。当开发人员在设计大型应用程序时必须要权衡好这类问题。最重要的部分是除非对吞吐量进行压力测试否则这些线程性能问题不易被发现。在设计大规模的多线程应用程序时压力测试是及其重要的。开发人员需要平衡这些因素:

  为了安全,尽可能多的使用同步。但这会让程序变得越来越慢,甚至比单线程版本还要慢。

  为了性能,尽可能少的使用同步。

多线程设计就是这两个因素之间不断的平衡。

 

下一篇介绍 避免死锁…