C# 中线程同步需要哪些工具

7/12/2022 5:03:04 PM
978
0

线程同步的工具有

volatile关键字、lock关键字、System.Threading.Interlocked、Monitor(监视器)、Mutex(互斥锁)、AutoResetEvent和 ManualResetEvent、ReaderWriterLock、SynchronizationAttribute、MethodImplAttribute

 

1、volatile关键字

volatile是最简单的一种同步方法,当然简单是要付出代价的。它只能在变量一级做同步,volatile的含义就是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我。因此,当多线程同时访问该变量时,都将直接操作主存,从本质上做到了变量共享。

但 volatile 并不能实现真正的同步,因为它的操作级别只停留在变量级别,而不是原子级别。如果是在单处理器系统中,是没有任何问题的,变量在主存中没有机会被其他人修改,因为只有一个处理器,这就叫作 processor Self-Consistency。但在多处理器系统中,可能就会有问题。 每个处理器都有自己的data cache,而且被更新的数据也不一定会立即写回到主存。所以可能会造成不同步,但这种情况很难发生,因为 cache 的读写速度相当快,flush 的频率也相当高,只有在压力测试的时候才有可能发生,而且几率非常非常小。

简单来说 volatile 关键字是告诉C#编译器和JIT编译器,不对 volatile 标记的字段做任何的缓存。确保字段读写都是原子操作,最新值。

从功能上看起到锁的作用,但它不是锁, 它的原子操作是基于CPU本身的,非阻塞的。 因为32位CPU执行赋值指令,数据传输最大宽度4个字节。

所以只要在4个字节以下读写操作的,32位CPU都是原子操作,volatile 是利用这个特性来保证其原子操作的。

这样的目的是为了提高JIT性能效率,对有些数据进行缓存了(多线程下)。

      //正确

      public volatile Int32 score1 = 1;

      //报错

      public volatile Int64 score2 = 1;

 如上,我们尝试定义了8个字节长度score2,会抛出异常。  因为8个字节32位CPU就分成2个指令执行了,所以就无法保证其原子操作了。

如果把编译平台改成64位,同样不可以使用,C#限制4个字节以下的类型字段才能用volatile。

一种方法是使用特定平台的整数类型 IntPtr,这样 volatile 即可作用到64位上了。

volatile 多数情况下很有用处的,毕竟锁的性能开销还是很大的。可以把当成轻量级的锁,根据具体场景合理使用,能提高不少程序性能。

线程中的 Thread.VolatileRead 和 Thread.VolatileWrite 是 volatile 以前的版本。

 

2、lock关键字

加锁使多个线程同一时间只有一个线程可以调用该方法,其他线程被阻塞。

  1. 使用引用类型,值类型加锁时会装箱,产生一个新的对象。
  2. 使用private修饰,使用public时易产生死锁。(使用lock(this),lock(typeof(实例))时,该类也应该是private)
  3. string不能作为锁对象。
  4. 不能在lock中使用await关键字

lock 只能在进程内锁,不能跨进程,内部走的是混合构造,先自旋再转成内核构造。

如果被锁定的方法是静态的,那么这个锁必须是静态类型。这样就是在全局锁定了该方法,不管该类有多少个实例,都要排队执行。

如果被锁定的方法不是静态的,那么不能使用静态类型的锁,因为被锁定的方法是属于实例的,只要该实例调用锁定方法不产生损坏就可以,不同实例间是不需要锁的。这个锁只锁该实例的方法,而不是锁所有实例的方法.*

class ThreadSafe
{
 private static object _locker = new object();
 
  void Go()
  {
    lock (_locker)
    {
      ......//共享数据的操作 (Static Method),使用静态锁确保所有实例排队执行
    }
  }

private object _locker2=new object();
  void GoTo()
  {
    lock(_locker2)
    //共享数据的操作,非静态方法,是用非静态锁,确保同一个实例的方法调用者排队执行
  }
}

同步对象可以兼作它lock的对象
如:

class ThreadSafe
{
 private List <string> _list = new List <string>(); 
  void Test()
  {
    lock (_list)
    {
      _list.Add ("Item 1");
    }
  }
}

3、System.Threading.Interlocked对象

如果一个变量被多个线程修改,读取。可以用Interlocked

计算机上不能保证对一个数据的增删是原子性的,因为对数据的操作也是分步骤的:

  1. 将实例变量中的值加载到寄存器中。
  2. 增加或减少该值。
  3. 在实例变量中存储该值。

Interlocked为多线程共享的变量提供原子操作。
Interlocked提供了需要原子操作的方法:

int i = 0 ;
System.Threading.Interlocked.Increment( ref i);
Console.WriteLine(i);
System.Threading.Interlocked.Decrement( ref i);
Console.WriteLine(i);
System.Threading.Interlocked.Exchange( ref i, 100 );
Console.WriteLine(i);
System.Threading.Interlocked.CompareExchange( ref i, 10 , 100 );

Interlocked对对象执行基本的原子操作,从而不用阻塞线程就可避免竞争条件。

对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步。Interlocked 提供了 Increment 、Decrement 、Add、Exchange 和CompareExchange 等基本数学操作的原子方法。使用 Increment 和 Decrement 可以保证对一个整数的加减为一个原子操作。Exchange 方法自动交换指定变量的值。CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。比较和交换操作也是按原子操作执行的。

MSDN 描述:为多个线程共享的变量提供原子操作。主要函数如下:

Interlocked.Increment  原子操作,递增指定变量的值并存储结果。

Interlocked.Decrement   原子操作,递减指定变量的值并存储结果。

Interlocked.Add     原子操作,添加两个整数并用两者的和替换第一个整数。

Interlocked.CompareExchange(ref a, b, c);  原子操作,a参数和c参数比较,相等b替换a,不相等不替换。



作者:LH_晴
链接:https://www.jianshu.com/p/ac41c25dbbb6
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4、Monitor关键词

lock其实是Monitors的简洁写法。

lock (x)  
{  
    DoSomething();  
}  

两者其实是一样的。

System.Object obj = (System.Object)x;  
System.Threading.Monitor.Enter(obj);  
try  
{  
    DoSomething();  
}  
finally  
{  
    System.Threading.Monitor.Exit(obj);  
} 

Monitor类提供了与lock类似的功能,不过与lock不同的是,它能更好的控制同步块,当调用了Monitor的Enter(Object o)方法时,会获取o的独占权,直到调用Exit(Object o)方法时,才会释放对o的独占权,可以多次调用Enter(Object o)方法,只需要调用同样次数的Exit(Object o)方法即可,Monitor类同时提供了TryEnter(Object o,[int])的一个重载方法,该方法尝试获取o对象的独占权,当获取独占权失败时,将返回false。

 另外,Monitor还提供了三个静态方法Monitor.Pulse(Object o),Monitor.PulseAll(Object o)和Monitor.Wait(Object o ) ,用来实现一种唤醒机制的同步。

           Monitor.Enter(obj); //在指定对象上获取排他锁

           Monitor.Wait(obj);  //释放对象上的锁并阻止当前线程,直到它重新获取该锁

           Monitor.Exit(obj);  //释放指定对象上的排他锁

           Monitor.Enter(obj);

          Monitor.Pulse(obj); //通知等待队列中的线程锁定对象状态的更改

           Monitor.Exit(obj);

           Monitor.Enter(obj);

           Monitor.PulseAll(obj);//通知所有的等待线程对象状态的更改

           Monitor.Exit(obj);

但使用 lock 通常比直接使用 Monitor 更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。Monitor 类是通过在 finally 中调用 Exit 来实现的。实际上,lock 关键字是 Monitor 类用例的一个语法糖,Lock 在IL会生成 Monitor。

 

5、Mutex

 在使用上,Mutex与上述的Monitor比较接近,不过Mutex不具备Wait,Pulse,PulseAll的功能,因此,我们不能使用Mutex实现类似的唤醒的功能。不过Mutex有一个比较大的特点,Mutex是跨进程的,因此我们可以在同一台机器甚至远程的机器上的多个进程上使用同一个互斥体。尽管Mutex也可以实现进程内的线程同步,而且功能也更强大,但这种情况下,还是推荐使用Monitor,因为Mutex类是win32封装的,所以它所需要的互操作转换更耗资源。

class Program
    {
      //实例化一个互斥锁
        public static Mutex mutex = new Mutex();

        static void Main(string[] args)
        {
            for (int i = 0; i < 3; i++)
            {
              //在不同的线程中调用受互斥锁保护的方法
                Thread test = new Thread(MutexMethod);
                test.Start();
            }
            Console.Read();
        }

        public static void MutexMethod()
        {
           Console.WriteLine("{0} 请求获取互斥锁", Thread.CurrentThread.Name);
           mut.WaitOne();
           Console.WriteLine("{0} 已获取到互斥锁", Thread.CurrentThread.Name);     
           Thread.Sleep(1000);
           Console.WriteLine("{0} 准备释放互斥锁", Thread.CurrentThread.Name);
            // 释放互斥锁
           mut.ReleaseMutex();
           Console.WriteLine("{0} 已经释放互斥锁", Thread.CurrentThread.Name);
        }
    }

互斥锁在不同的进程间实现线程同步

使用互斥锁实现一个一次只能启动一个应用程序的功能。

    public static class SingleInstance
    {
        private static Mutex m;

        public static bool IsSingleInstance()
        {
            //是否需要创建一个应用
            Boolean isCreateNew = false;
            try
            {
               m = new Mutex(initiallyOwned: true, name: "SingleInstanceMutex", createdNew: out isCreateNew);
            }
            catch (Exception ex)
            {
               
            }
            return isCreateNew;
        }
    }

 

互斥锁的带有三个参数的构造函数

  1. initiallyOwned: 如果initiallyOwned为true,互斥锁的初始状态就是被所实例化的线程所获取,否则实例化的线程处于未获取状态。
  2. name:该互斥锁的名字,在操作系统中只有一个命名为name的互斥锁mutex,如果一个线程得到这个name的互斥锁,其他线程就无法得到这个互斥锁了,必须等待那个线程对这个线程释放。
  3. createNew:如果指定名称的互斥体已经存在就返回false,否则返回true。

信号和句柄

lock和mutex可以实现线程同步,确保一次只有一个线程执行。但是线程间的通信就不能实现。如果线程需要相互通信的话就要使用AutoResetEvent,ManualResetEvent,通过信号来相互通信。它们都有两个状态,终止状态和非终止状态。只有处于非终止状态时,线程才可以阻塞。它可以用来激活和挂起线程。

6、SemaphoreSlim(信号量)

SemaphoreSlim 类是 Semaphore 类的轻量级版本,该类限制了同时访问同一个资源的线程数量。

信号量就像一个夜总会:它有确切的容量,并被保镖控制。一旦满员,就没有人能再进入,其他人必须在外面排队。那么在里面离开一个人后,队头的人就可以进入。

 SemaphoreSlim_semaphore =newSemaphoreSlim(4);

  _semaphore.Wait();      //阻止当前线程,直至它可进入 SemaphoreSlim 为止

  _semaphore.Release(); //退出 SemaphoreSlim 一次

SemaphoreSlim 使用了混合模式,其允许我们在等待时间很短的情况下无需使用上下文切换。然而,有一个叫做 Semaphore 的 SemaphoreSlim 类的老版本,该版本使用纯粹的内核时间(kernel-time)方式,一般没必要使用它,除非是非常重要的场景。我们可以创建一个具名的 semaphore,就像一个具名的 mutex 一样,从而在不同的程序中同步线程;mutex 对一个资源进行锁,semaphore 则是对多个资源进行加锁;semaphore 是由 windows 内核维持一个 int32 变量的线程计数器,线程每调用一次,计数器减一,释放后对应加一,超出的线程则排队等候。

Mutex、Semaphore  需要先把托管代码转成本地用户模式代码、再转换成本地内核代码。

当释放后需要重新转换成托管代码,性能会有一定的损耗,所以尽量在需要跨进程的场景再使用。

 

6、AutoResetEvent 和  ManualResetEvent

同步事件有两种:AutoResetEvent和 ManualResetEvent。它们之间唯一不同的地方就是在激活线程之后,状态是否自动由终止变为非终止。AutoResetEvent自动变为非终止,就是说一个AutoResetEvent只能激活一个线程。而ManualResetEvent要等到它的Reset方法被调用,状态才变为非终止,在这之前,ManualResetEvent可以激活任意多个线程。

可以调用WaitOne、WaitAny或WaitAll来使线程等待事件。它们之间的区别可以查看MSDN。当调用事件的 Set方法时,事件将变为终止状态,等待的线程被唤醒。

 

AutoResetEvent 类采用的是内核模式,所以等待的时间不能太长。ManualResetEventSlim 类更好,因为它使用的混合模式,可以在线程间以更灵活的方式传递信号。

AutoResetEvent 事件像一个旋转门,一次只允许一人通过。ManualResetEventSlim 的整个工作方式有点像人群通过大门。

ManualResetEventSlim 是 ManualResetEvent 的混合版本,一直保持大门敞开直到手动调用 Reset 方法(相当于关闭了大门),当调用 Set 方法时,相当于打开了大门从而允许准备好的线程接收信号并继续工作。

 如果我们需要全局事件,则可以使用 EventWaitHandle 类,其是 AutoResetEvent 和 ManualResetEvent 类的基类。

可以调用 WaitOne、WaitAny 或 WaitAll 来使线程等待事件。它们之间的区别可以查看MSDN。当调用事件的 Set 方法时,事件将变为终止状态,等待的线程被唤醒。

 如果在Set被调用的时候没有线程等待,那么句柄就会一直处于打开状态直到有线程调用了WaitOne操作。这种行为避免了竞争条件--当一个线程还没来得急释放而另一个线程就开始进入的情况。因此重复的调用Set操作一个"轮盘"哪怕是没有等待线程也不会一次性的让所有线程进入。

 

class ThreadSafe 
{  
    static AutoResetEvent autoEvent;  

    static void Main()  
    {  
        //使AutoResetEvent处于非终止状态
        autoEvent = new AutoResetEvent(false);  

        Console.WriteLine("主线程运行...");  
        Thread t = new Thread(DoWork);  
        t.Start();  

        Console.WriteLine("主线程sleep 1秒...");  
        Thread.Sleep(1000);  

        Console.WriteLine("主线程释放信号...");  
        autoEvent.Set();  
    }  

     static void DoWork()  
    {  
        Console.WriteLine("  t线程运行DoWork方法,阻塞自己等待main线程信号...");  
        autoEvent.WaitOne();  
        Console.WriteLine("  t线程DoWork方法获取到main线程信号,继续执行...");  
    }  

}  

//输出
//主线程运行...
//主线程sleep 1秒...
//  t线程运行DoWork方法,阻塞自己等待main线程信号...
//主线程释放信号...
//  t线程DoWork方法获取到main线程信号,继续执行...


AutoResetEvent在调用了Set方法后,会自动的将信号由释放(终止)改为阻塞(非终止),一次只有一个线程会得到释放信号。而ManualResetEvent在调用Set方法后不会自动的将信号由释放(终止)改为阻塞(非终止),而是一直保持释放信号,使得一次有多个被阻塞线程运行,只能手动的调用Reset方法,将信号由释放(终止)改为阻塞(非终止),之后的再调用Wait.One方法的线程才会被再次阻塞。

 

public class ThreadSafe
{
    //创建一个处于非终止状态的ManualResetEvent
    private static ManualResetEvent mre = new ManualResetEvent(false);

    static void Main()
    {
        for(int i = 0; i <= 2; i++)
        {
            Thread t = new Thread(ThreadProc);
            t.Name = "Thread_" + i;
            t.Start();
        }

        Thread.Sleep(500);
        Console.WriteLine("\n新线程的方法已经启动,且被阻塞,调用Set释放阻塞线程");

        mre.Set();

        Thread.Sleep(500);
        Console.WriteLine("\n当ManualResetEvent处于终止状态时,调用由Wait.One方法的多线程,不会被阻塞。");

        for(int i = 3; i <= 4; i++)
        {
            Thread t = new Thread(ThreadProc);
            t.Name = "Thread_" + i;
            t.Start();
        }

        Thread.Sleep(500);
        Console.WriteLine("\n调用Reset方法,ManualResetEvent处于非阻塞状态,此时调用Wait.One方法的线程再次被阻塞");
  

        mre.Reset();

        Thread t5 = new Thread(ThreadProc);
        t5.Name = "Thread_5";
        t5.Start();

        Thread.Sleep(500);
        Console.WriteLine("\n调用Set方法,释放阻塞线程");

        mre.Set();
    }


    private static void ThreadProc()
    {
        string name = Thread.CurrentThread.Name;

        Console.WriteLine(name + " 运行并调用WaitOne()");

        mre.WaitOne();

        Console.WriteLine(name + " 结束");
    }
}


//Thread_2 运行并调用WaitOne()
//Thread_1 运行并调用WaitOne()
//Thread_0 运行并调用WaitOne()

//新线程的方法已经启动,且被阻塞,调用Set释放阻塞线程

//Thread_2 结束
//Thread_1 结束
//Thread_0 结束

//当ManualResetEvent处于终止状态时,调用由Wait.One方法的多线程,不会被阻塞。

//Thread_3 运行并调用WaitOne()
//Thread_4 运行并调用WaitOne()

//Thread_4 结束
//Thread_3 结束

///调用Reset方法,ManualResetEvent处于非阻塞状态,此时调用Wait.One方法的线程再次被阻塞

//Thread_5 运行并调用WaitOne()
//调用Set方法,释放阻塞线程
//Thread_5 结束

6、CountdownEvent

6、Barrier

6、SpinWait  SpinLock

6、WaitHandle 

在 WaitHandle类中SignalAndWait、WaitAll、WaitAny及WaitOne这几个方法都有重载形式,其中除WaitOne之外都是静态的。WaitHandle方法常用作同步对象的基类。WaitHandle对象通知其他的线程它需要对资源排他性的访问,其他的线程必须等待,直到WaitHandle不再使用资源和等待句柄没有被使用。

 WaitHandle方法有多个Wait的方法,这些方法的区别如下:

 WaitAll:等待指定数组中的所有元素收到信号。

 WaitAny:等待指定数组中的任一元素收到信号。

 WaitOne:当在派生类中重写时,阻塞当前线程,直到当前的 WaitHandle 收到信号。

 这些wait方法阻塞线程直到一个或者更多的同步对象收到信号。

下面的代码示例演示当主线程使用 WaitHandle 类的静态 M:System.Threading.WaitHandle.WaitAny(System.Threading.WaitHandle[]) 和Waitall方法等待任务完成时,两个线程可以如何完成后台任务。
 

using System;
using System.Threading;

public sealed class App 
{
static WaitHandle[] waitHandles = new WaitHandle[] 
    {
new AutoResetEvent(false),
new AutoResetEvent(false)
    };
static Random r = new Random();

static void Main() 
    {
DateTime dt = DateTime.Now;
Console.WriteLine("Main thread is waiting for BOTH tasks to complete.");
ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask), waitHandles[0]);
ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask), waitHandles[1]);
WaitHandle.WaitAll(waitHandles);
// The time shown below should match the longest task.
Console.WriteLine("Both tasks are completed (time waited={0})", 
            (DateTime.Now - dt).TotalMilliseconds);
dt = DateTime.Now;
Console.WriteLine();
Console.WriteLine("The main thread is waiting for either task to complete.");
ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask), waitHandles[0]);
ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask), waitHandles[1]);
int index = WaitHandle.WaitAny(waitHandles);
Console.WriteLine("Task {0} finished first (time waited={1}).",
index + 1, (DateTime.Now - dt).TotalMilliseconds);
    }

static void DoTask(Object state) 
    {
AutoResetEvent are = (AutoResetEvent) state;
int time = 1000 * r.Next(2, 10);
Console.WriteLine("Performing a task for {0} milliseconds.", time);
Thread.Sleep(time);
are.Set();
    }
}

 

 

 

 

7、ReaderWriterLock

 在考虑资源访问的时候,惯性上我们会对资源实施lock机制,但是在某些情况下,我们仅仅需要读取资源的数据,而不是修改资源的数据,在这种情况下获取资源的独占权无疑会影响运行效率,因此.Net提供了一种机制,使用ReaderWriterLock进行资源访问时,如果在某一时刻资源并没有获取写的独占权,那么可以获得多个读的访问权,单个写入的独占权,如果某一时刻已经获取了写入的独占权,那么其它读取的访问权必须进行等待,参考以下代码:

    class Program
    {
        // 创建一个对象
        public static ReaderWriterLock readerwritelock = new ReaderWriterLock();
        static void Main(string[] args)
        {
            //创建一个线程读取数据
            Thread t1 = new Thread(Write);
           // t1.Start(1);
            Thread t2 = new Thread(Write);
            //t2.Start(2);
            // 创建10个线程读取数据
            for (int i = 3; i < 6; i++)
            {
                Thread t = new Thread(Read);
              //  t.Start(i);
            }

            Console.Read();

        }

        // 写入方法
        public static void Write(object i)
        {
            // 获取写入锁,20毫秒超时。
            Console.WriteLine("线程:" + i + "准备写...");
            readerwritelock.AcquireWriterLock(Timeout.Infinite);
            Console.WriteLine("线程:" + i + " 写操作" + DateTime.Now);
            // 释放写入锁
            Console.WriteLine("线程:" + i + "写结束...");
            Thread.Sleep(1000);
            readerwritelock.ReleaseWriterLock();

        }

        // 读取方法
        public static void Read(object i)
        {
            Console.WriteLine("线程:" + i + "准备读...");

            // 获取读取锁,20毫秒超时
            readerwritelock.AcquireReaderLock(Timeout.Infinite);
            Console.WriteLine("线程:" + i + " 读操作" + DateTime.Now);
            // 释放读取锁
            Console.WriteLine("线程:" + i + "读结束...");
            Thread.Sleep(1000);

            readerwritelock.ReleaseReaderLock();

        }
    }
//分别屏蔽writer和reader方法。可以更清晰的看到 writer被阻塞了。而reader没有被阻塞。

//屏蔽reader方法
//线程:1准备写...
//线程:1 写操作2017/7/5 17:50:01
//线程:1写结束...
//线程:2准备写...
//线程:2 写操作2017/7/5 17:50:02
//线程:2写结束...

//屏蔽writer方法
//线程:3准备读...
//线程:5准备读...
//线程:4准备读...
//线程:5 读操作2017/7/5 17:50:54
//线程:5读结束...
//线程:3 读操作2017/7/5 17:50:54
//线程:3读结束...
//线程:4 读操作2017/7/5 17:50:54
//线程:4读结束...

lock允许同一时间只有一个线程执行。而ReaderWriterLock允许同一时间有多个线程可以执行读操作,或者只有一个有排它锁的线程执行写操作

8、SynchronizationAttribute

 当我们确定某个类的实例在同一时刻只能被一个线程访问时,我们可以直接将类标识成Synchronization的,这样,CLR会自动对这个类实施同步机制,实际上,这里面涉及到同步域的概念,当类按如下设计时,我们可以确保类的实例无法被多个线程同时访问
  1). 在类的声明中,添加System.Runtime.Remoting.Contexts.SynchronizationAttribute属性。
    2). 继承至System.ContextBoundObject
    需要注意的是,要实现上述机制,类必须继承至System.ContextBoundObject,换句话说,类必须是上下文绑定的。
    一个示范类代码如下:

[System.Runtime.Remoting.Contexts.Synchronization]
public class SynchronizedClass : System.ContextBoundObject
{

}

9、MethodImplAttribute

如果临界区是跨越整个方法的,也就是说,整个方法内部的代码都需要上锁的话,使用MethodImplAttribute属性会更简单一些。这样就不用在方法内部加锁了,只需要在方法上面加上 [MethodImpl(MethodImplOptions.Synchronized)] 就可以了,MehthodImpl和MethodImplOptions都在命名空间System.Runtime.CompilerServices 里面。但要注意这个属性会使整个方法加锁,直到方法返回,才释放锁。因此,使用上不太灵活。如果要提前释放锁,则应该使用Monitor或lock。我们来看一个例子:

[MethodImpl(MethodImplOptions.Synchronized)]
public void DoSomeWorkSync()
{
Console.WriteLine( " DoSomeWorkSync() -- Lock held by Thread " +
Thread.CurrentThread.GetHashCode());
Thread.Sleep( 1000 );
Console.WriteLine( " DoSomeWorkSync() -- Lock released by Thread " +
Thread.CurrentThread.GetHashCode());
}
public void DoSomeWorkNoSync()
{
Console.WriteLine( " DoSomeWorkNoSync() -- Entered Thread is " +
Thread.CurrentThread.GetHashCode());
Thread.Sleep( 1000 );
Console.WriteLine( " DoSomeWorkNoSync() -- Leaving Thread is " +
Thread.CurrentThread.GetHashCode());
}

[STAThread]
static void Main( string [] args)
{
MethodImplAttr testObj = new MethodImplAttr();
Thread t1 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync));
Thread t2 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync));
t1.Start();
t2.Start();
Thread t3 = new Thread( new ThreadStart(testObj.DoSomeWorkSync));
Thread t4 = new Thread( new ThreadStart(testObj.DoSomeWorkSync));
t3.Start();
t4.Start();

Console.ReadLine();
}

这里,我们有两个方法,我们可以对比一下,一个是加了属性MethodImpl的DoSomeWorkSync(),一个是没加的DoSomeWorkNoSync()。在方法中Sleep(1000)是为了在第一个线程还在方法中时,第二个线程能够有足够的时间进来。对每个方法分别起了两个线程,我们先来看一下结果:

可以看出,对于线程1和2,也就是调用没有加属性的方法的线程,当线程2进入方法后,还没有离开,线程1有进来了,这就是说,方法没有同步。我们再来看看线程3和4,当线程3进来后,方法被锁,直到线程3释放了锁以后,线程4才进来。

 

 

 

全部评论



提问