首页 > 代码库 > C#的线程同步

C#的线程同步

1.在单线程中,我们每次只能做一件事情。

而在多线程中,其实最本质的也是一次只能做一件事情,只是CPU给定的时间片完成之后,切换到另一个线程,然后这个分配CPU的时间片完成之后,再次切换到另一个线程,如此反复,其间切换的速度很快,给人一种同时进行的错觉而已。

但在多线程中,往往会发生两个线程抢占资源的问题,所以我们需要防止这些资源访问的冲突。

C#提供线程同步机制来防止资源访问的冲突,其中主要用到lock关键字、Monitor类和Mutex类



2.线程同步机制

应用程序中使用多线程的一个好处是每个线程都可以异步执行。

线程同步是指并发线程高效、有序地访问共享资源所采用的技术。

所谓同步,是指某一时刻只有一个线程可以访问资源,只有当资源所有者主动放弃了代码或资源的所有权时,其他线程才可以使用这些资源



3.使用lock关键字实现线程同步

lock关键字可以用来确保代码块完成运行,而不会被其他线程中断,它是通过在代码块运行期间为给定对象获取互斥锁来实现。

lock语句以关键字lock开头,它有一个作为参数的对象,在该参数的后面还有一个一次只能有一个线程执行的代码块。

语法格式:

Object thisLock = new Object();

lock(thisLock){//要运行的代码块}

提供给lock语句的参数只是用来唯一标识由多个线程共享的资源,所以可以使任意类实例,实际上,此参数通常标识需要进行线程同步的资源。

避免锁定public类型或不受应用程序控制的对象实例。尽量避免锁定公共数据类型,锁定字符串尤其危险。所以,最好锁定不会被暂留的私有对象或受保护的对象。


说明:lock语句使用Monitor类实现,等效于try/finally语句块,使用lock关键字通常比直接使用Monitor类更可取。首先,lock更简洁;其次,lock确保了及时受保护的代码引发异常,也可以释放基础监听器,这是通过finally关键字来实现,无论是否引发一场,它都执行关联的代码块。

注意:如果在静态方法中使用lock关键字,则不能使用this

static void Main(string[] args)
{
       lock(new Program())
      { 
            Console.WriteLine("锁定线程");
            Console.ReadLine();
       }
}



4.使用Monitor驱动对象实现线程同步

Monitor类提供了同步对对象的访问机制,通过向单个线程授予对象锁来控制对对象的访问。对象锁提供限制访问代码块(临界区)的能力。当一个线程拥有对象锁时,其他任何线程都不能获取该锁。


Monitor类的主要功能:

(1)根据需要与某个对象相关联

(2)它是未绑定的,可以直接从任何上下文调用它

(3)不能创建Monitor类的实例


Monitor类的常用方法:

Enter          在指定的对象上获取排他锁

Exit           释放指定对象上的排他锁

Pulse          通知等待队列中的线程锁定对象状态的更改

PulseAll       通知所有的等待线程对象状态的更改

TryEnter       试图获取指定对象的排他锁

Wait           释放对象上的锁并阻止当前线程,直到它重新获取该锁


使用Monitor类锁定的是对象(引用类型)而不是值类型


实例代码:

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



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

Mutex类与监视器类似,与监视器不同的是,Mutex类可以用来是跨进程的线程同步

使用WaitHandle.WaitOne方法请求互斥体的所属权

拥有互斥体的线程可以在对WaitOne方法的重复调用中请求相同的互斥体而不会阻止其执行,但是记住,线程必须要调用同样多次的ReleaseMutex方法来释放互斥体的所属权。

Mutex类强制线程标识,因此互斥体只能由获得它的线程释放。


Mutex类常用的方法

Close               在派生类中被重写时,释放由当前WaitHandle持有的所有资源

OpenExisting        打开现有的已命名的互斥体

ReleaseMutex        释放Mutex一次

SignalAndWait       原子操作的形式,向一个WaitHandle发出信号并等待另一个

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

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

WaitOne             在派生类中重写,阻止当前线程,知道当前WaitHandle收到信号


实现Mutex类线程同步的步骤:

1.创建一个Mutex对象,构造函数中比较常用的有public Mutex(bool initallyOwned)

参数制定了创建该对象的线程是否希望立即获取其所有权,当在一个资源得到保护的类中创建Mutex对象,常常将该参数设置为false

2.在需要单线程访问的地方调用其等待方法,等待方法请求Mutex对象的所有权。如果该所有权被另一个线程拥有,则阻塞请求线程,并放到等待队列中,请求线程将保持阻塞,直到Mutex对象受到其所有者线程发出的将其释放的信号为止。


6.实例代码

创建程序,自定义lockThread方法,利用Mutex对象的WaitOne方法阻止当前线程,然后再调用Mutex对象的ReleaseMutex方法释放Mutex对象,即释放当前线程。最后在Main方法中通过对象调用lockThread方法

class Program
    {
        static void Main(string[] args)
        {
            Program myProgram = new Program();		                //实例化类对象
            myProgram.LockThread();					//调用锁定线程方法
            Console.ReadLine();
        }
        void lockThread()
        {
            Mutex myMutex = new Mutex(false);				//创建Mutex对象
            myMutex.WaitOne();                                          //阻止当前线程
            Console.WriteLine("锁定线程以实现线程同步");
            myMutex.ReleaseMutex();					//释放Mutex对象
        }
    }


总的来说,线程同步的东西还是要多读几遍,才能明白。而且要不断地实践锻炼才能知道lock,Monitor类和Mutex类是怎么用的,各自的区别优点以及缺点在哪里。

感觉学无止境啊,继续Fighting吧