c# 多线程详解

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。当程序中有多个线程运行,就称为多线程。

多线程在windows form等编程是经常会用到的,它可以让UI主线程不阻塞,同时更有效的利用cpu资源,有更好的用户体验。但是当多个线程访问共享资源(比如静态变量)时,就需要考虑到线程同步的问题。比如当有一个线程对共享资源在写的时候,别的线程需要等待写完成后再获取资源,但是它会影响代码的执行效率。

线程的委托方法有ThreadStart和ParameterizedThreadStart,ParameterizedThreadStart委托需要方法传递object的参数。

Thread方法

Start(),并非立即执行,取决于操作系统的线程管理策略。

Sleep(),阻塞方法,当线程阻塞时,它不会占用CPU的时间。

Interrupt(),唤醒出于睡眠或者等待中的线程,如果调用Interrupt时,线程不是睡眠或等待状态时,会立即跑出异常。

Join(),一个线程的操作需要等待另一个线程执行完毕后才继续执行。阻塞方法,不占用CPU时间。

IsBackground属性,线程分前台线程和后台线程,当前台线程执行完成后,程序立即退出,不会等待后台线程执行。

Abort(),强制退出一个线程,会抛出ThreadAbortExcepion,但是即使不捕获它,也不会影响到整个进程。


线程同步

有lock,Monitor,Mutex,EventWaitHandle,Semaphore等同步机制。

lock和Monitor是.NET用一个特殊结构实现的,Monitor对象是完全托管的、完全可移植的,并且在操作系统资源要求方 面可能更为有效,同步速度较快,但不能跨进程同步。lock(Monitor.Enter和Monitor.Exit方法的封装),主要作用是锁定临界区,使临 界区代码只能被获得锁的线程执行。Monitor.Wait和Monitor.Pulse用于线程同步,类似信号操作,个人感觉使用比较复杂,容易造成死锁。

lock是我们经常用的,也是最简单的。

private object objLock = new object();

public void AddOne() // 没有加锁,会出现重复情况。
        {
            lock (objLock)
            {
                count++;
                
                Console.WriteLine("current value is " + count);
            }
        }


比如上面的代码中count是共享资源,因为多个线程访问AddOne方法,会在线程切换中使加一的情况出现缺少连续的数的情况,所以我们需要用lock关键字把对资源操作的代码给包括起来。这样当一个线程获得objLock对象的锁后, 它会去执行lock内的代码,直到执行完成,而别的线程只能等待,直到锁被释放。

其实lock是对Monitor的封装,但是Monitor功能比lock要多。

lock(obj)
{
}
等价为:
try
{    
      Monitor.Enter(obj)
}
catch()
{}
finally
{
      Monitor.Exit(obj)
}
Monitor,有Enter,Exit,Wait,Pulse等方法,也有TryEnter方法。它可以实现多线程之间的先后执行顺序。

Enter,Exit比较简单,获得锁和释放锁。

Wait是释放锁,并把自己阻塞,一直到获得锁后再执行。

 Thread t = new Thread(new ThreadStart(Produce));
           t.Start();
 Thread tt = new Thread(new ThreadStart(Consume));
           tt.Start();
 public void Produce()
        {
            while (true)
            {
               Monitor.Enter(obj);//获得锁
                value++;
                Monitor.Pulse(obj);//通知线程锁状态改变,没有Pulse的话,线程就会死锁。
                Monitor.Wait(obj);//等待并释放锁
            }
        }

        public void Consume()
        {
            while (true)
            {
                Monitor.Enter(obj);
                Console.WriteLine(value);
                Monitor.Pulse(obj);
                Monitor.Wait(obj);
            }
        }

如上代码,实现的内容是控制一个线程加一,另一个才输出值。它可以控制线程执行的先后顺序。如果使用lock,或者Monitor的Enter和Exit,不能保证执行的顺序是按照加一后输出。但是Monitor也比较复杂,经过我的测试,Enter,Pulse,Wait是需要一起使用的,否则就死锁了。

死锁,死锁就是两个或者两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。例如线程1拥有锁A,需要锁B,线程2拥有锁B,需要锁A,这样就会引起死锁。这里可以使用Monitor的TryEnter()方法解决,当无法获取锁时,释放自己已经有的锁。

使用WaitHandle,Mutex,EventWaitHandle,Semaphore继承了WaitHandle

互斥锁Mutex和事件对象EventWaitHandle属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模 式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步

Mutex(互斥体)

<pre name="code" class="csharp">        Mutex t = new Mutex();

        void ThreadEntry()
        {
            t.WaitOne();
            Console.WriteLine("thread sync test");
            t.ReleaseMutex();
        }

 

互斥锁Mutex类似于一个接力棒,拿到接力棒的线程才可以开始跑,当然接力棒一次只属于一个线程(Thread Affinity),如果这个线程不释放接力棒(Mutex.ReleaseMutex),那么没办法,其他所有需要接力棒运行的线程都知道能等着看热闹。Mutex可以用于多进程同步,当多进程共享内存时,可以使用Mutex进行进程间同步。这里不做讨论。

EventWaitHandle

ManualResetEvent和AutoResetEvent都继承自EventWaitHandle。他们有2个状态,收到信号和未收到信号。

ManualResetEvent

 <pre name="code" class="csharp">public class ThreadSyncTest
    {
        ManualResetEvent mer = new ManualResetEvent(false);//参数false,表示一开始在mer.WaitOne这是等待的。如果是true,则一开始就不需要等待,可以直接往下执行

        public ThreadSyncTest()
        {
            ThreadStart ts = new ThreadStart(ThreadEntry);
            new Thread(ts).Start();
        }

        public void ThreadEntry()
        {
            while (true)
            {
                mer.WaitOne();
                Console.WriteLine("Run");
                Thread.Sleep(500);
            }
        }

        public void Start()
        {
            mer.Set();//给mer一个信号,继续往下执行
        }

        public void Stop()
        {
            mer.Reset();//使线程停止在mer.WaitOne()处,无法往下执行
        }
    }

 reset方法,使ManualResetEvent 处于未收到信号状态,无法往下执行。 

set方法,使ManualResetEvent处于收到信号状态,可以往下执行。

AutoResetEvent与ManualResetEvent的区别是WaitOne方法,合并了Reset方法。调用WaitOne()方法使线程停止,需要再次Set,才能继续执行。

Semaphore, 限制可同时访问某一资源或资源池的线程数。




  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值