• 热门专题

多线程(线程同步1)

作者:编码之道  发布日期:2016-12-22 20:36:20
Tag标签:线程  
  •   在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示:

    执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 使用ManualResetEventSlim构造 使用CountdownEvent构造 使用Barrier构造 使用ReaderWriterLockSlim构造 使用SpinWait构造

     一、执行基本的原子操作

      在这一小节中,我们将学习如何在没有阻塞线程(blocking threads)发生的情况下,在一个对象上执行基本的原子操作并能阻止竞争条件(race condition)的发生。操作步骤如下所示:

    1、使用Visual Studio 2015创建一个新的控制台应用程序。

    2、双击打开“Program.cs”文件,编写代码如下所示:

     1 using System;
     2 using System.Threading;
     3 using static System.Console;
     4 
     5 namespace Recipe01
     6 {
     7     abstract class CounterBase
     8     {
     9         public abstract void Increment();
    10 
    11         public abstract void Decrement();
    12     }
    13 
    14     class Counter : CounterBase
    15     {
    16         private int count;
    17 
    18         public int Count => count;
    19 
    20         public override void Increment()
    21         {
    22             count++;
    23         }
    24 
    25         public override void Decrement()
    26         {
    27             count--;
    28         }
    29     }
    30 
    31     class CounterNoLock : CounterBase
    32     {
    33         private int count;
    34 
    35         public int Count => count;
    36 
    37         public override void Increment()
    38         {
    39             Interlocked.Increment(ref count);
    40         }
    41 
    42         public override void Decrement()
    43         {
    44             Interlocked.Decrement(ref count);
    45         }
    46     }
    47 
    48     class Program
    49     {
    50         static void TestCounter(CounterBase c)
    51         {
    52             for (int i = 0; i < 100000; i++)
    53             {
    54                 c.Increment();
    55                 c.Decrement();
    56             }
    57         }
    58 
    59         static void Main(string[] args)
    60         {
    61             WriteLine('Incorrect counter');
    62 
    63             var c1 = new Counter();
    64 
    65             var t1 = new Thread(() => TestCounter(c1));
    66             var t2 = new Thread(() => TestCounter(c1));
    67             var t3 = new Thread(() => TestCounter(c1));
    68             t1.Start();
    69             t2.Start();
    70             t3.Start();
    71             t1.Join();
    72             t2.Join();
    73             t3.Join();
    74 
    75             WriteLine($'Total count: {c1.Count}');
    76             WriteLine('--------------------------');
    77 
    78             WriteLine('Correct counter');
    79 
    80             var c2 = new CounterNoLock();
    81 
    82             t1 = new Thread(() => TestCounter(c2));
    83             t2 = new Thread(() => TestCounter(c2));
    84             t3 = new Thread(() => TestCounter(c2));
    85             t1.Start();
    86             t2.Start();
    87             t3.Start();
    88             t1.Join();
    89             t2.Join();
    90             t3.Join();
    91 
    92             WriteLine($'Total count: {c2.Count}');
    93         }
    94     }
    95 }

    3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

      在第63行代码处,我们创建了一个非线程安全的Counter类的一个对象c1,由于它是非线程安全的,因此会发生竞争条件(race condition)。

      在第65~67行代码处,我们创建了三个线程来运行c1对象的“TestCounter”方法,在该方法中,我们按顺序对c1对象的count变量执行自增和自减操作。由于c1不是线程安全的,因此在这种情况下,我们得到的counter值是不确定的,我们可以得到0值,但多运行几次,多数情况下会得到不是0值得错误结果。

      在多线程(基础篇)中,我们使用lock关键字锁定对象来解决这个问题,但是使用lock关键字会造成其他线程的阻塞。但是,在本示例中我们没有使用lock关键字,而是使用了Interlocked构造,它对于基本的数学操作提供了自增(Increment)、自减(Decrement)以及其他一些方法。

    二、使用Mutex构造

       在这一小节中,我们将学习如何使用Mutex构造同步两个单独的程序,即进程间的同步。具体步骤如下所示:

    1、使用Visual Studio 2015创建一个新的控制台应用程序。

    2、双击打开“Program.cs”文件,编写代码如下所示:

     1 using System;
     2 using System.Threading;
     3 using static System.Console;
     4 
     5 namespace Recipe02
     6 {
     7     class Program
     8     {
     9         static void Main(string[] args)
    10         {
    11             const string MutexName = 'Multithreading';
    12 
    13             using (var m = new Mutex(false, MutexName))
    14             {
    15                 // WaitOne方法的作用是阻止当前线程,直到收到其他实例释放的处理信号。
    16                 // 第一个参数是等待超时时间,第二个是否退出上下文同步域。
    17                 if (!m.WaitOne(TimeSpan.FromSeconds(10), false))
    18                 {
    19                     WriteLine('Second instance is running!');
    20                     ReadLine();
    21                 }
    22                 else
    23                 {
    24                     WriteLine('Running!');
    25                     ReadLine();
    26                     // 释放互斥资源
    27                     m.ReleaseMutex();
    28                 }
    29             }
    30 
    31             ReadLine();
    32         }
    33     }
    34 }

    3、编译代码,执行两次该程序,运行效果如下所示:

    第一种情况的运行结果:

    第二种情况的运行结果:

      在第11行代码处,我们定义了一个mutex(互斥量)的名称为“Multithreading”,并在第13行代码处将其传递给了Mutex类的构造方法,该构造方法的第一个参数initialOwner我们赋值为false,这允许程序获得一个已经被创建的mutex。如果没有任何线程锁定互斥资源,程序只简单地显示“Running”,然后等待按下任何键以释放互斥资源。

      如果我们启动该程序的第二个实例,如果在10秒内我们没有在第一个实例下按下任何按钮以释放互斥资源,那么在第二个实例中就会显示“Second instance is running!”,如第一种情况的运行结果所示。如果在10内我们在第一个实例中按下任何键以释放互斥资源,那么在第二个实例中就会显示“Running”,如第二种情况的运行结果所示。

    三、使用SemaphoreSlim构造

      在这一小节中,我们将学习如何在SemaphoreSlim构造的帮助下,限制同时访问资源的线程数量。具体步骤如下所示:

    1、使用Visual Studio 2015创建一个新的控制台应用程序。

    2、双击打开“Program.cs”文件,编写代码如下所示:

     1 using System;
     2 using System.Threading;
     3 using static System.Console;
     4 using static System.Threading.Thread;
     5 
     6 namespace Recipe03
     7 {
     8     class Program
     9     {
    10         static SemaphoreSlim semaphore = new SemaphoreSlim(4);
    11 
    12         static void AccessDatabase(string name, int seconds)
    13         {
    14             WriteLine($'{name} waits to access a database');
    15             semaphore.Wait();
    16             WriteLine($'{name} was granted an access to a database');
    17             Sleep(TimeSpan.FromSeconds(seconds));
    18             WriteLine($'{name} is completed');
    19             semaphore.Release();
    20         }
    21 
    22         static void Main(string[] args)
    23         {
    24             for(int i = 1; i <= 6; i++)
    25             {
    26                 string threadName = 'Thread' + i;
    27                 int secondsToWait = 2 + 2 * i;
    28                 var t = new Thread(() => AccessDatabase(threadName, secondsToWait));
    29                 t.Start();
    30             }
    31         }
    32     }
    33 }

    3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

     

      在第10行代码处,我们创建了一个SemaphoreSlim的实例,并对该构造方法传递了参数4,该参数指定了可以有多少个线程同时访问资源。然后,我们启动了6个不同名字的线程。每个线程都试着获取对数据库的访问,但是,我们限制了最多只有4个线程可以访问数据库,因此,当4个线程访问数据库后,其他2个线程必须等待,直到其他线程完成其工作后,调用“Release”方法释放资源之后才能访问数据库。

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规