https://mp.weixin.qq.com/s?__biz=MzI4OTI5NjIyMA==&mid=2247494511&idx=1&sn=aca96a30255ad2bd92bda6b6c6ab9904&chksm=ec33f15cdb44784a46aa8cdb30bb220eba04096f0ec09cf91a71bb553e01d3366a23f41ad38f&mpshare=1&scene=23&srcid=1114f3kNwJYzshOTqTvmUkjY&sharer_sharetime=1636882787371&sharer_shareid=3fbf667202e3571656770d85ec2d851f#rd

1. 使用 C#中的 lock 关键字实现线程同步

lock 关键字可以用来确保代码块完整运行,而不会被其他线程中断,它是通过在代码块运行期间为给定对象获取互斥锁来实现的。
lock 语句以关键字 lock 开头,它有一个作为参数的对象,在该参数的后面还有一个一次只能由一个线程执行的代码块。
lock 语句的语法格式如下:

Object thisLock = new Object();
lock(thisLock)
{     
     //要运行的代码块
}

提供给 lock 语句的参数必须为基于引用类型的对象,该对象用来定义锁的范围。严格地说,提供给 lock 语句的参数只是用来唯一标识由多个线程共享的资源,所以它可以是任意类实例。然而,该参数通常表示需要进行线程同步的资源。例如,如果一个容器对象被多个线程使用,则可以将该容器传递给 lock 语句,而 lock 语句中的代码块将访问该容器,如果其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。
通常,最好避免锁定 public 类型或不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则lock(this)可能会有问题,因为不受控制的代码也可能会锁定该对象,这将可能导致死锁,即两个或更多个线程等待释放同一个对象,出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题,锁定字符串尤其危险。因为字符串被公共语言运行库(CLR)“暂留”,这意味着整个程序中任何给定字符串都只有一个实例。因此,如果在应用程序进程中的任何具有相同内容的字符串上放置锁,那么将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。
【例8】 创建一个控制台应用程序,其中自定义一个 LockThread 方法,该方法中使用 lock 关键字锁定当前线程,然后在 Main 方法中通过 Program 的类对象调用 LockThread 自定义方法。
代码如下:

static void Main(string[] args)
{
    Program myProgram = new Program();       //实例化类对象 
    myProgram.LockThread();                 //调用锁定线程方法 
}
void LockThread()
{
    lock(this)                             //锁定当前线程,以实现同步
    {
        Console.WriteLine("锁定线程以实现线程同步");
    } 
}

2. 使用 Monitor 类实现线程同步

Monitor类提供了同步对象的访问机制,它通过向单个线程授予对象锁来控制对对象的访问,对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象锁时,其他任何线程都不能获取该锁。
Monitor 类的主要功能如下:
(1)它根据需要与某个对象相关联。
(2)它是未绑定的,也就是说可以直接从任何上下文调用它。
(3)不能创建 Monitor 类的实例。
Monitor 类的常用方法及说明如表4 所示。
表4 Monitor 类的常用方法及说明
图片
注意
使用 Monitor 类锁定的是对象(即引用类型)而不是值类型。
【例9】创建一个控制台应用程序,其中自定义一个LockThread 方法,该方法中首先使用 Monitor 类的 Enter 方法锁定当前的线。然后再调用 Monitor 类的 Exit 方法释放当前线程,最后在 Main 方法中通过 Program 的类对象调用 LockThread 自定义方法。
代码如下:

static void Main(string[] args)
{
     Program myPrograme = new Program();            //实例化类对象 
     myProgram.LockThread();                        //调用锁定线程方法 
}
void LockThread()
{
     Monitor.Enter(this);                          //锁定当前线程 
     Console.WriteLine("锁定线程以实现线程同步");
     Monitor.Exit(this);                           //释放当前线程

说明
从上述两个例子来看,这两个例子实现的功能是相同的,但似乎使用 LOCK 关键字更简单一些,那为何还要使用 Monitor 类呢?因为使用 Monitor 类有更好的控制能力。例如,它可以使用 Wait 方法指示活动的线程等待一段时间,当线程完成操作时,还可以使用 Pulse 方法或 PulseAll方法通知等待中的线程。

3. 使用 Mutex 类实现线程同步

当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex 类是同步基元,它只向一个线程授予对共享资源的独占访问。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体,Mutex 类与监视器类似,它防止多个线程在某一时间同时执行某个代码块,然而与监视器不同的是,Mutex 类可以用来使跨进程的线程同步。
可以使用 WaitHandle.WaitOne 方法请求互斥体的所属权,拥有互斥体的线程可以在对 WaitOne方法的重复调用中请求相同的互斥体而不会阻止其执行,但线程必须调用同样多次数的 ReleaseMutex 方法以释放互斥体的所属权。Mutex 类强制线程标识,因此互斥体只能由获得它的线程释放。
当用于进程间同步时,Mutex 称为“命名 Mutex”,因为它将用于另一个应用程序,因此它不能通过全局变量或静态变量共享。必须给它指定一个名称,才能使两个应用程序访问同一个 Mutex 对象。
Mutex 类的常用方法及说明如表5 所示。
表5 Mutex 类的常用方法及说明
图片
使用 Mutex 类实现线程同步很简单,首先实例化一个 Mutex 类对象,它的构造函数中比较常用的有 public Mutex(bool initallyOwned)。其中,参数 initallyOwned 指定了创建该对象的线程是否希望立即获得其所有权,当在一个资源得到保护的类中创建 Mutex 类对象时,常将该参数设置为 false。然后在需要单线程访问的地方调用其等待方法,等待方法请求 Mutex 对象的所有权。这时,如果该所有权被另一个线程所拥有,则阻塞请求线程,并将其放入等待队列中,请求线程将保持阻塞,直到 Mutex 对象收到了其所有者线程发出将其释放的信号为止。所有者线程在终止时释放 Mutex 对象,或者调用 ReleaseMutex 方法来释放 Mutex 对象。
说明
尽管 Mutex 类可以用于进程内的线程同步,但是使用Monitor 类通常更为可取,因为 Monitor 监视器是专门为.NET Framework 而设计的,因而它可以更好地利用资源。相比之下, Mutex 类是 Win32 构造的包装。尽管Mutex 类比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算机资源。
【例10】创建一个控制台应用程序,其中自定义了一个 LockThread 方法,该方法中首先使用 Mutex 类对象的 WaitOne 方法阻止当前线程。然后再调用 Mutex 类对象的 ReleaseMutex 方法释放 Mutex对象,即释放当前的线程。最后在 Main 方法中通过 Program 的类对象调用 LockThread 自定义方法。
代码如下:

static void Main(string[] args){     
    Program myProgram = new Program();                //实例化类对象  
    myProgram.LockThread();                           //调用锁定线程方法 
}
void LockThread(){   
        Mutex myMutex=new Mutex(false);  //实例化 Mutex 类对象
        myMutex.WaitOne();                                 //阻止当前线程  
        Console.WriteLine("锁定线程以实现线程同步");
        myMutex.ReleaseMutex();                            //释放 Mutex 对象
    }
文档更新时间: 2021-11-14 19:12   作者:admin