多线程 锁

Synchronization属性和ContextBoundObject类(锁整个类)

Synchronization属性和ContextBoundObject类,这两个一起使用可以让一个类的实例处于同步环境中。

自动同步,CLR确保了同一时刻只有一个线程可以执行继承自ContextBoundObject实例中的代码。锁的作用域——这里是继承自ContextBoundObject实例对象,被称为同步环境。

自动同步不能用于静态类型的成员

死锁

是指多个线程共享某些资源时,都占用一部分资源,而且都在等待对方释放另一部分资源,从而导致程序停滞不前的情况。

private static void Test1(){

       lock(o1){

              …

              lock(o2){

                     …

               }
        }
}

private static void Test2(){

       lock(o2){

              …

              lock(o1){

                     …

            }
        }
}

锁们(信号量 开关)

锁单处:

lock(锁单个变量,自动释放和加锁)

在同一时刻只有一个线程可以锁定同步对象(在这里是locker),任何竞争的的其它线程都将被阻止,直到这个锁被释放。如果有大于一个的线程竞争这个锁,那么他们将形成称为“就绪队列”的队列,以先到先得的方式授权锁。

一个等候竞争锁的线程被阻止其状态为WaitSleepJoin状态。

锁多处或单处(自由手动释放):

WaitHandle(父类)

WaitHandle是一个抽象类,WaitHandler有三个子类,这三个子类分别是:EventWaitHandle,Mutex(跨进程),Semaphore(控制总量)。WaitAny,WaitAll,SignalAndWait都是WaitHandle类的静态方法,都是用来获取释放的被阻止的的线程,WaitHandle调用Set 时触发。WaitAny(数组内任意一个WaitHandle被Set)。WaitAll(数组内全部的WaitHandle被Set)

 

 EventWaitHandle(跨进程,线程无关,任何线程都可以调用Release)

 EventWaitHandle是AutoResetEvent和ManualResetEvent的父类,可通过EventWaitHandle生成AutoResetEvent或者ManualResetEvent实例。

同时EventWaitHandle可以跨进程同步。EventWaitHandle以及它的派生类AutoResetEvent和ManualResetEvent都是线程无关的。任何线程都可以发信号给EventWaitHandle,以唤醒阻塞在上面的线程。

 ManualResetEvent(不自动关闭)

一旦调用了Set,阻塞的大门会一直敞开,所有线程调用WaitOne或者阻塞在WaitOne上的都将被放行。

当调用Reset()后大门才会关闭,线程将阻塞在WaitOne上。

AutoResetEvent(进入一个就自动关闭入口,自动控制阻塞)

AutoResetEvent就像一个用票通过的旋转门:插入一张票,让一个人通过。一个线程等待或阻止通过在门上调用WaitOne方法,票的插入则由调用Set方法。如果由许多线程调用WaitOne,在门前便形成了队列,任何(非阻止)线程要通过AutoResetEvent对象调用Set方法来释放一个被阻止的的线程。

与Mutex不同,AutoResetEvent是线程无关的。任何线程都可以发信号给EventWaitHandle,以唤醒阻塞在上面的线程。

Mutex(跨进程,线程相关,只有获取该Mutex的线程可以调用Release)

C#中Mutex是互斥锁,位于System.Threading 命名空间中,可跨进程互斥访问同一资源

互斥锁是一种互斥的同步对象,意味着同一时间有且仅有一个线程。Mutex适用于一个共享资源每次只能被一个线程访问的情况,也就是控制多个线程相互之间的联系,不产生冲突和重复。Mutex.waitone进入获取阶段,如果被获取就进入阻塞,直到获取Mutex的线程调用Mutex.release才可进入。 Mutex相对于Lock的不同在于,Lock跨线程,Mutex可跨进程。如果使用下面的方法初始化,true代表由当前线程获得初始所属权。“Program”代表这个Mutex的名字,操作系统里全局只存在一个名为“Program”的Mutex,这里会用来做跨进程间互斥访问同一资源

只有获得Mutex拥有权的线程才能执行ReleaseMutex()方法,否则就会引发异常。这就是所谓的线程相关性。

            bool flag = false;
            Mutex mutex = new Mutex(true,"Program",out flag);
 
            if (flag)
            {
                Console.Write("开始");
            }
            else
            {
                Console.Write("另一个程序正在运行");
                System.Threading.Thread.Sleep(5000);//线程挂起5秒钟
                Environment.Exit(1);//退出程序
            }

第一次运行 打印开始,如果这时再运行一次,则打印另一个程序正在运行。跟单例模型有点像。

其中有一个地方要注意,WaitOne(int time,bool out)  其中time为超时检测,超过时间后必返回,out为true时,超时后会退出同步域,为fales时,超时后是不会退出同步域的。问题在哪呢?问题在,如果你提前在一个线程M.waitOne占用了互斥体。这时如果你在其他线程调用这个M.WaitOne(int time,bool out)。由于Main线程已将占用了互斥体,因此阻塞。超时后如果你out填的是fales,则线程不会退出同步环境先调用的M.waitOne得等你后调用m.WaitOne(int time,bool out)这个的线程退出同步域,才能进入同步域。这个方法只有调用WaitOne的类使用了Synchronization(true)且继承自ContextBoundObject,该参数才有意义。

Semaphore(可控制总量,线程无关,任何线程都能调用Release)

信号量Semaphore常用的方法有两个WaitOne()Release(),Release()的作用是退出信号量并返回前一个计数,而WaitOne()则是阻止当前线程,直到当前线程的WaitHandle 收到信号。

以上厕所为例的话,信号量设置为5人, 如果同时有10人要上,则根据先到先得,前5人先进入,之后的人进入到waitOne,待前5人有人出来后,出来的时候调用Release,即释放一个信号量,其余人进入waitOne的,收到释放的一个信号量后,则可以进入一个人。Semaphore也是线程无关的。

public class Program
    {
        static Semaphore sema = new Semaphore(5, 5);
        static void Main(string[] args) 
        {
            for(int i = 0; i < cycleNum; i++)
            {
                Thread td = new Thread(testFun);
                td.Name = string.Format("编号{0}",i.ToString());
                td.Start(td.Name);
            }
            Console.ReadKey();
        }
        public static void testFun(object obj)
        {
            sema.WaitOne();
            Console.WriteLine(obj.ToString() + "进洗手间:" + DateTime.Now.ToString());
            Thread.Sleep(1000);
            Console.WriteLine(obj.ToString() + "出洗手间:" + DateTime.Now.ToString());
            sema.Release();
        }
    }

线程相关性


EventWaitHandle和Mutex两者虽然是派生自同一父类,但有着完全不同的线程相关性:

Mutex是“线程相关(Thread Affinity)”的。只有获得Mutex拥有权的线程才能执行ReleaseMutex()方法,否则就会引发异常。这就是所谓的线程相关性。

相反,EventWaitHandle以及它的派生类AutoResetEvent和ManualResetEvent都是线程无关的。任何线程都可以发信号给EventWaitHandle,以唤醒阻塞在上面的线程。

Semaphore也是线程无关的。

读锁与写锁 ReaderWriterLockSlim

个类型的实例对于并行的读操作是线程安全的,但是并行地更新操作则不是(并行地读与更新也不是)。使用一个独占锁来锁定所有可能的访问能够实现线程安全,但是当有很多的读操作而只是偶然的更新操作的时候,这就很不合理的限制了并发。在这样的情况下,ReaderWriterLockSlim类被设计成提供最大可能的锁定。

所以,当一个线程拥有一个WriteLock的时候,会阻塞所有其他线程获得读写锁。但是当没有线程获得WriteLock时,可以有多个线程同时获得ReadLock,进行读操作。

ReaderWriterLockSlim提供了下面四个方法来得到和释放读写锁:

public void EnterReadLock();

public void ExitReadLock();

public void EnterWriteLock();

public void ExitWriteLock();
 

UpgradeableReadLock  

在一个原子操作里面交换读写锁是非常有用的,比如,当某个item不在list中的时候,添加此item进去。最好的情况是,最小化写如锁的时间,例如像下面这样处理:

    1 获得一个读取锁

    2 测试list是否包含item,如果是,则返回

    3 释放读取锁

    4 获得一个写入锁

    5 写入item到list中,释放写入锁。

     但是在步骤3、4之间,当另外一个线程可能偷偷修改List(比如说添加同样一个Item),ReaderWriterLockSlim通过提供第三种锁来解决这个问题,这就是upgradeablelock。一个可升级锁和readlock类似,只是它能够通过一个原子操作,被提升为writelock。使用方法如下:

1    调用 EnterUpgradeableReadLock
2   读操作
3  调用 EnterWriteLock 
4   写操作
5    调用ExitWriteLock 
6     其他读取的过程
7   调用ExitUpgradeableReadLock
     

第三步的时候,通过一个原子操作,释放了readlock 并获得了一个新的writelock.

upgradeablelocks 和readlocks之间另外还有一个重要的区别,

(1)一个upgradeablelock能够和任意多个readlock共存,但是一个时刻,只能有一个upgradeablelock自己被使用。即同一时刻只有一个线程能够获取upgradeablelock所有权。

(2)在获取了upgradeablelock所有权后,允许继续获取写锁的所有权。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值