C#多线程总结

C#多线程总结

多线程的概念

  1. 操作系统能够优先访问CPU,并能够调整不同程序访问CPU的优先级,避免某一个程序独占CPU的情况发生。
  2. 可以认为线程是一个虚拟进程,用于独立运行一个特定的程序。
  3. 线程会消耗大量的操作系统资源,多个线程共享一个物理处理器将导致操作系统忙于管理这些线程,而无法运行程序
  4. 在单核cpu上并行执行计算任务是没有意义的。
  5. 在多核cpu上可以使用多线程有效的利用多个cpu的计算能力。这需要组织多个线程间的通信和相互同步。

线程的基本操作

线程的生命周期包括:创建线程 、挂起线程、线程等待、终止线程

创建线程

通过new 一个Thread对象并指定线程执行函数创建线程。调用Start方法开启线程

/// <summary>
        /// 线程启动函数
        /// </summary>
        static void PrintNums()
        {
            Console.WriteLine("starting ...........");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i);
            }
            Console.WriteLine("end......");
        }
        /// 创建一个线程
        public static void Main()
        {
            Thread thread = new Thread(PrintNums);
            thread.Start();
            PrintNums();//主线程调用
        }

暂停当前线程

通过在线程函数中调用Thread.Sleep()暂停当前线程,使线程进入休眠状态。此时线程会占用尽可能少的CPU时间。

//暂停线程 
        static void PrintNumsWithDelay()
        {
            Console.WriteLine("starting ...........");
            //Log("当前线程状态 :"  + Thread.CurrentThread.ThreadState.ToString());
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));  //每次暂停一秒
                Console.WriteLine(i);
            }
            Console.WriteLine("end......");
        }

        //创建一个线程并暂停
        public static void Test1()
        {
            Thread t = new Thread(PrintNumsWithDelay);
            t.Start();
            PrintNums();//主线程调用
        }

线程等待

假设有线程t,在主程序中调用了t.Join()方法,该方法允许我们等待直到线程t完成。当线程t完成 时,主程序会继续运行。借助该技术可以实现在两个线程间同步执行步骤。第一个线程会等待另一个线程完成后再继续执行。第一个线程等待时是处于阻塞状态(正如暂停线程中调用 Thread.Sleep方法一样)

/// <summary>
        /// 在主线程中等待线程执行玩
        /// </summary>
        public static void Main()
        {
            Thread t1 = new Thread(PrintNumsWithDelay);
            t1.Start();
            t1.Join(); //等待线程t1执行完成,程序会在这里阻塞
            Console.WriteLine("Thread t1 finished");
        }

终止线程

通过调用Thread.Abort()方法强制终止线程。这会给线程注入ThreadAbortExeception方法,导致线程被终结。这是一个非常危险的操作, 任何时刻发生并可能彻底摧毁应用程序。另外,使用该技术也不一定总能终止线程。目标线程可以通过处理该异常并调用Thread.ResetAbort方法来拒绝被终止。因此并不推荐使用,Abort方法来关闭线程 。

/// <summary>
        /// 终止线程  非常危险,不推荐使用,也不一定能够终止线程
        /// </summary>
        public static void Main()
        {
            Thread t = new Thread(PrintNumsWithDelay);
            t.Start();
            //5s之后终止线程t
            Thread.Sleep(5000);
            t.Abort();
            Console.WriteLine("Thread t has been Abort");
        }

获取线程状态

线程状态位于Thread对象的ThreadState属性中。ThreadState属性是一个C#枚举对象。刚开始线程状态为ThreadState.Unstarted,然后我们启动线程,线程状态会从ThreadState.Running变为ThreadState. WaitSleepJoin。 其中:请注意始终可以通过Thread.CurrentThread静态属性获得当前Thread对象。

/// <summary>
        /// 线程状态
        /// </summary>
        public static void Test5()
        {
            Thread t1 = new Thread(PrintNumsWithDelay);
            Log("t1线程状态 :"  + t1.ThreadState.ToString());
            t1.Start();
            Log("t1线程状态 :"  + t1.ThreadState.ToString());
            t1.Join(); //等待线程t1执行完成,程序会在这里阻塞
            Log("t1线程状态 :"  + t1.ThreadState.ToString());
            Console.WriteLine("Thread t1 finished");
            Log("t1线程状态 :"  + t1.ThreadState.ToString());
        }

线程优先级

通过设置Thread.Priority属性给线程对象设置优先级 ThreadPriority.Highest (最高优先级)、 ThreadPriority.Lowest(最低优先级)。通常优先级更高的线程将获取到更多cpu时间。

/// <summary>
        /// 线程优先级 
        /// </summary>
        class ThreadSample
        {
            private bool isStop = false;

            public void Stop()
            {
                isStop = true;
            }

            public void CountNums()
            {
                long counter = 0;
                while (!isStop)
                {
                    counter++;
                }
                Console.WriteLine("{0} with {1,11} priority has a count = {2,13}"
                    ,Thread.CurrentThread.Name,Thread.CurrentThread.Priority,
                    counter.ToString());
            }
        }
        static void RunThreads()
        {
            //启动两个线程t1 t2
            var sample = new ThreadSample();
            Thread t1 = new Thread(sample.CountNums);
            t1.Name = "Thread1";
            Thread t2 = new Thread(sample.CountNums);
            t2.Name = "Thread2";
            //设置线程的优先级
            t1.Priority = ThreadPriority.Highest;  //t1为最高优先级
            t2.Priority = ThreadPriority.Lowest;  //t2为最低优先级
            //启动
            t1.Start();
            t2.Start();

            //主线程阻塞2s
            Thread.Sleep(TimeSpan.FromSeconds(2));
            sample.Stop();  //停止计数
            //等待按键按下
            Console.ReadKey();
        }
        /// <summary>
        /// 线程优先级,决定该线程可以占用多少cpu时间
        /// </summary>
        public static void Test6()
        {
            Log("当前线程的优先级 priority = " + Thread.CurrentThread.Priority.ToString());
            Log("在所有核上运行");

            RunThreads();

            Thread.Sleep(TimeSpan.FromSeconds(2));
            Log("在单个核上运行");
            //在该进程下的线程只能在一个核上运行
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
            //再次执行
            RunThreads();

            /*
             结果:多核时高优先级的线程通常会比低优先级的线程多执行次数 但是大体接近
                单核的时候竞争就更激烈了,用有高优先级的线程会占用更多的cpu时间,而留给低优先级的线程的
                cpu时间就更少了。
             * 在所有核上运行
                Thread1 with     Highest priority has a count =     583771892
                Thread2 with      Lowest priority has a count =     444097830
                在单个核上运行
                Thread2 with      Lowest priority has a count =      32457242
                Thread1 with     Highest priority has a count =    6534967709
             * 
             */
        }

前台线程和后台线程

  1. 当主程序启动时定义了两个不同的线程。默认情况下,显式创建的线程是前台线程。通过手动的设置Thread对象的IsBackground属性为ture来创建一个后台线程。
  2. 前台线程与后台线程的主要区别: 进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
  3. 如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。
/// <summary>
           /// 前台线程和后台线程 
           /// </summary>

           class ThreadSample2
           {
               private int iterCount = 0;

               public ThreadSample2(int count)
               {
                   this.iterCount = count;
               }

               public void CountNum()
               {
                   for (int i = 0; i <= iterCount; i++)
                   {
                       Thread.Sleep(500); //挂起0.5s
                       //输出次数
                       Console.WriteLine("{0} prints {1}",Thread.CurrentThread.Name,i);
                   }

                   Log("Thread Finished : " + Thread.CurrentThread.Name);
               }

           }
           public static void Test7()
           {
               ThreadSample2 samp1 = new ThreadSample2(10);
               ThreadSample2 samp2 = new ThreadSample2(20);

               //启动两个线程t1,t2,并讲其中一个设置为后台线程 默认是前台线程
               Thread t1 = new Thread(samp1.CountNum);
               t1.Name = "Foreground";
               Thread t2 = new Thread(samp2.CountNum);
               t2.Name = "Background";
               t2.IsBackground = true;  //设置为后台线程

               //启动
               t1.Start();
               t2.Start();

               //进程会等所有的前台程序结束完之后才结束;如果只剩下后台程序,则进程会直接结束
           }

向线程中传递参数

启动线程的时候需要向线程函数中传递参数,一般有三种方式。

  1. 将线程函数声明为一个类的成员函数,通过类的成员变量来传递参数。
  2. 声明一个静态函数当作线程的执行函数,该函数接受一个object类型的参数param,这个参数可以通过Thread.Start(param)传递到线程中。
  3. 通过lambda表达式的闭包机制传递参数,原理等同于1。C#编辑器会帮我们实现这个类。
/// <summary>
           /// 向线程中传递参数
           /// </summary>

           public static  void CountNum(int iterCount)
           {
               for (int i = 0; i <= iterCount; i++)
               {
                   Thread.Sleep(500); //挂起0.5s
                   //输出次数
                   Console.WriteLine("{0} prints {1}",Thread.CurrentThread.Name,i);
               }

               Log("Thread Finished : " + Thread.CurrentThread.Name);
           }

           //方法3:通过object类型的参数传递,参数parma在Start()函数中传递
           public static void Count(object param)
           {
               CountNum((int) param);
           }


           public static void Test8()
           {
               Thread t1 = new Thread(Count);
               t1.Start(6);  //传递参数

               //方法3 通过lamda表达式
               int num = 8;
               Thread t2 = new Thread(()=>{ CountNum(num);});
               num = 12;
               Thread t3 = new Thread(() => { CountNum(num);});
               t2.Start();
               t3.Start();

           }

线程锁的使用

  1. 当多个线程同时访问一个资源的时候,容易形成竞争条件,导致错误的产生。为了确保不会发生这种情况,需要保证当前线程1在访问变量A的时候,其他线程必须等待直到线程1完成当前操作。
  2. 如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。这,可能会导致严重的性能问题。
  3. 可以使用lock关键字来进行加锁操作
public abstract class BaseCounter
           {
              public abstract void Add();
              public abstract void Del();
              public abstract long GetRes();
           }

           /// <summary>
           /// 线程不安全的计数器
           /// </summary>
           public class Counter : BaseCounter
           {
               private long counter;
               public override void Add()
               {
                   counter++;
               }

               public override void Del()
               {
                   counter--;
               }

               public override long GetRes()
               {
                   return counter;
               }
           }

           /// <summary>
           /// 线程安全的计数器
           /// </summary>
           public class ThreadSafeCounter : BaseCounter
           {
               /// <summary>
               /// 线程锁对象 
               /// </summary>
               private readonly  object lockObj = new object();

               private long counter;
               public override void Add()
               {
                   lock (lockObj)  //锁定一个对象,需要访问该对象的所有其他线程就会处于阻塞状态,并等待直到该对象接触锁定
                   {
                       counter++;
                   }

               }

               public override void Del()
               {
                   lock (lockObj)
                   {
                       counter--;
                   }
               }

               public override long GetRes()
               {
                   return counter;
               }
           }

           /// <summary>
           /// 线程函数 测试计数器
           /// </summary>
           /// <param name="c"></param>
           static void TestCounter(BaseCounter c)
           {
               for (int i = 0; i < 10000000; i++)
               {
                   c.Add();
                   c.Del();
               }
           }

           public static void TestLock()
           {
               //测试不安全的计数器
               //创建3个线程同时对计数器进行加减
               Counter c = new Counter();
               Thread t1 = new Thread(() => { TestCounter(c);});
               Thread t2 = new Thread(() => { TestCounter(c);});
               Thread t3 = new Thread(() => { TestCounter(c);});
               t1.Start();
               t2.Start();
               t3.Start();
               t1.Join();
               t2.Join();
               t3.Join();
               //等三个线程都执行完之后打印结果 看是否为0
               Console.WriteLine("count =   " + c.GetRes());


               //测试线程安全的计数器
               //创建3个线程同时对计数器进行加减
               ThreadSafeCounter c1 = new ThreadSafeCounter();
                t1 = new Thread(() => { TestCounter(c1);});
                t2 = new Thread(() => { TestCounter(c1);});
                t3 = new Thread(() => { TestCounter(c1);});
               t1.Start();
               t2.Start();
               t3.Start();
               t1.Join();
               t2.Join();
               t3.Join();
               //等三个线程都执行完之后打印结果 看是否为0
               Console.WriteLine("count =   " + c1.GetRes());

           }

死锁的产生

什么是死锁:

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。

产生死锁的原因

竞争资源

系统中的资源可以分为两类:

  1. 竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
  2. 是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁

进程间推进顺序非法

1. 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
2. 例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁

一个死锁的例子

static void LockTooMuch(object lock1, object lock2)
        {
            lock (lock1)  //锁定lock1对象
            {
                Thread.Sleep(1000); //线程挂起1s
                lock (lock2) //试图获取lock2对象的锁定
                {
                    Console.WriteLine("成功获取到lock2对象的锁定");
                }
            }
        }

        /// <summary>
        /// 造成一个死锁
        /// </summary>
        public static void Test()
        {
            //定义两个锁定对象
            object lock1 = new object();
            object lock2 = new object();

            //开启线程
            Thread t1 = new Thread(() => {LockTooMuch(lock1,lock2); });
            t1.Start();

            //在主线程中锁定lock2对象
            lock (lock2)
            {
                Console.WriteLine("这将要产生一个死锁");
                Thread.Sleep(1000);
                lock (lock1)  //试图访问lock1
                {
                    Console.WriteLine("成功获取到lock1对象的锁定");
                }
            }
        }

通过Monitor的超时锁定机制避免死锁

直接使用Monitor类。其拥有TryEnter方法,该方法接受一个超时参数。如果在我们能够获取被lock保护的资源之前,超时参数过期,则该方法会返回 false.

/// <summary>
        /// 使用Monitor避免死锁
        /// </summary>
        public static void Test2()
        {
            //定义两个锁定对象
            object lock1 = new object();
            object lock2 = new object();

            //开启线程
            Thread t1 = new Thread(() => {LockTooMuch(lock1,lock2); });
            t1.Start();

            //在主线程中使用Monitor类来锁定lock2对象
            //在主线程中锁定lock2对象
            lock (lock2)
            {
                Console.WriteLine("使用Monitor.TryEnter避免死锁,有一个超时时间的设定 超时返回false");
                Thread.Sleep(1000);
                //设置5s的超时时间
                if(Monitor.TryEnter(lock1,TimeSpan.FromSeconds(5)))
                {
                    Console.WriteLine("成功获取到lock1对象的锁定");
                }
                else
                {
                    Console.WriteLine("获取lock1对象失败,");
                }
            }
        }

线程中异常处理

一般来说不要在线程中抛出异常,而是在线程代码中使用try/catch代码块

/// <summary>
        /// 在线程中抛出异常,错误的示范
        /// </summary>
        static void BadFaultThread()
        {
            Console.WriteLine("开启一个线程");
            Thread.Sleep(1000);
            throw new Exception("Boom!");
        }

        /// <summary>
        /// 在线程函数中处理异常,正确的示范
        /// </summary>
        static void FaultThread()
        {
            try
            {
                Console.WriteLine("开启一个线程");
                Thread.Sleep(1000);
                throw new Exception("Boom!");
            }
            catch (Exception e)
            {
                Console.WriteLine("捕获异常:" + e.Message);
                Console.WriteLine("处理异常");
            }
        }

        public static void Test()
        {
            Thread t1 = new Thread(FaultThread);
            t1.Start();
            t1.Join();

            Thread.Sleep(1000);
            //尝试捕获线程中抛出的异常,失败
            try
            {
                Thread t2 = new Thread(BadFaultThread);
                t2.Start();
            }
            catch (Exception e)
            {
                //这里不会捕获到发生的异常
                Console.WriteLine("捕获异常:" + e.Message);
            }
        }

线程同步

什么是线程同步

当一个线程执行递增和递减操作时,其他线程需要依次等待。这种常见问题通常被称为线程同步

线程同步的实现方式

  1. 如果无须共享对象,那么就无须进行线程同步。可以通过重新设计程序来除移共享状态,从而去掉复杂的同步构造。请尽可能避免在多个线程间使用单一对象
  2. 如果必须使用共享的状态,第二种方式是只使用原子操作。一个操作只占用一个量子的时间,一次就可以完成。所以只有当前操作完成后,其他线程才能执行其他操作
  3. 内核模式:将等待的线程置于阻塞状态。线程处于阻塞状态时,只会占用尽可能少的CPU时间。这意味着将引入至少一次所谓的上下文切换( context switch),上下文切换是指操作系统的线程调度器。该调度器会保存等待的线程的状态并切换到另一个线程,依次恢复等待的线程的状态。这需要消耗相当多的资源。
  4. 用户模式:将线程设置成简单的等待,不用切换到阻塞状态。适用于线程只需要等待一小段时间按的情况下。虽然线程等待时会浪费CPU时间,但我们节省了上下文切换耗费的CPU时间。该方式非常轻量,速度很快,但如果线程需要等待较长时间则会浪费大量的CPU时间。
  5. 混合模式:混合模式先尝试使用用户模式等,待,如果线程等待了足够长的时间,则会切换到阻塞状态以节省CPU资源。

AutoResetEvent

AutoResetEvent是.net线程简易同步方法中的一种。AutoResetEvent 常常被用来在两个线程之间进行信号发送

两个线程共享相同的AutoResetEvent对象,线程1可以通过调用AutoResetEvent对象的WaitOne()方法进入等待状态,然后另外一个线程2通过调用AutoResetEvent对象的Set()方法取消等待的状态。

AutoResetEvent如何工作的

在内存中保持着一个bool值,如果bool值为False,则使线程阻塞,反之,如果bool值为True,则使线程退出阻塞。当我们创建AutoResetEvent对象的实例时,我们在函数构造中传递默认的bool值,以下是实例化AutoResetEvent的例子。

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

WaitOne 方法

该方法阻止当前线程继续执行,并使线程进入等待状态以获取其他线程发送的信号。WaitOne将当前线程置于一个休眠的线程状态。WaitOne方法收到信号后将返回True,否则将返回False。

autoResetEvent.WaitOne();

WaitOne方法的第二个重载版本是等待指定的秒数。如果在指定的秒数后,没有收到任何信号,那么后续代码将继续执行。

static void ThreadMethod()
{
    while(!autoResetEvent.WaitOne(TimeSpan.FromSeconds(2)))  //收到信号返回TRUE 否则一直FALSE
    {
        Console.WriteLine("Continue");
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }

    Console.WriteLine("Thread got signal");
}

这里我们传递了2秒钟作为WaitOne方法的参数。在While循环中,autoResetEvent对象等待2秒,然后继续执行。当线程取得信号,WaitOne返回为True,然后退出循环,打印"Thread got signal"的消息。

Set 方法

AutoResetEvent Set方法发送信号到等待线程以继续其工作,以下是调用该方法的格式。

autoResetEvent.Set();

AutoResetEvent例子

下面的例子展示了如何使用AutoResetEvent来释放线程。在Main方法中,我们用Task Factory创建了一个线程,它调用了GetDataFromServer方法。调用该方法后,我们调用AutoResetEvent的WaitOne方法将主线程变为等待状态。在调用GetDataFromServer方法时,我们调用了AutoResetEvent对象的Set方法,它释放了主线程,并控制台打印输出dataFromServer方法返回的数据。

public static  class TestAutoResetEvent
    {
        //实例化以一个AutoResetEvent对象 用false初始化
        public static AutoResetEvent autoReset = new AutoResetEvent(false);
        private static string data;


        public static void Test()
        {
            //启动一个线程
            Thread t = new Thread(() => { GetDataFromServer(); });
            t.Start();
            Console.WriteLine("主线程等待中。。。");
            autoReset.WaitOne();  //主线程阻塞 等待接收信号
            //接收到信号之后继续执行下面代码
            Console.WriteLine("Get A signal  = " + data);

        }

        /// <summary>
        /// 模拟从服务器获取数据
        /// </summary>
        static void GetDataFromServer()
        {
            Thread.Sleep(3000); //线程阻塞3s
            data = "GetDataFinish";
            //获取完数据之后发送信号 解除主线程的锁定
            autoReset.Set();  //发送信号
        }


    }

基本的原子操作

指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。原子操作不用阻塞线程就可以避免竞争关系。

使用Interlocked类

Interlocked提供了Increment, Decrement和Add等基本数学操作的原子方法,从而帮助我们,在编写Counter类时无需使用锁。

/// <summary>
        /// 线程函数 测试计数器
        /// </summary>
        /// <param name="c"></param>
        static void TestCounter(AtomCounter c)
        {
            for (int i = 0; i < 10000000; i++)
            {
                c.Add();
                c.Del();
            }
        }

        /// <summary>
        /// 使用Interlocked类进行++、--的原子操作
        /// </summary>
        public class AtomCounter
        {
            private long counter = 0;
            public  void Add()
            {
                Interlocked.Increment(ref counter);
            }

            public  void Del()
            {
                Interlocked.Decrement(ref counter);
            }
            public  long GetRes()
            {
                return counter;
            }
        }

        /// <summary>
        /// 原子操作  
        /// </summary>
        public static  void Test()
        {
            AtomCounter c1 = new AtomCounter();
            var t1 = new Thread(() => { TestCounter(c1);});
            var t2 = new Thread(() => { TestCounter(c1);});
            var t3 = new Thread(() => { TestCounter(c1);});
            t1.Start();
            t2.Start();
            t3.Start();
            t1.Join();
            t2.Join();
            t3.Join();
            //等三个线程都执行完之后打印结果 看是否为0
            Console.WriteLine("count =   " + c1.GetRes());
        }

使用SemaphoreSlim信号量

SemaphoreSlim 类为一个轻量、快速的信号量,可在等待时间预计很短的情况下,用于在单个进程内的等待。

该类限制了同时访问同一个资源的线程数量。信号量可用于生产者、消费者线程,其中一个线程始终增加信号量计数,另一个始终减少信号量计数

//创建一个信号量 设置允许的并发线程的初始数量和最大数量
        private static int studentCount = 5;  //有5个学生
        private static int seatNum = 2;   //有2个座位
        static SemaphoreSlim _sem = new SemaphoreSlim(seatNum,seatNum);
        //模拟学生占座到座位上吃饭
        static void Eat(string name, int seconds)
        {
            _sem.Wait();  //排队中等有座位空出来
            try
            {
                Console.WriteLine("{0} 占了一个位置,准备吃饭", name);
                Thread.Sleep(TimeSpan.FromSeconds(seconds)); //吃饭时间
            }
            finally
            {

                Console.WriteLine("{0} 吃完饭了,让出位置",name);
                _sem.Release(); //释放信号量,让出位置
            }

        }
        //信号量
        public static void TestSemaphore()
        {
            //初始化5个线程来模拟学生吃饭,最多同时2个人能占座吃饭
            for (int i = 0; i < studentCount; i++)
            {
                string threadName = "Student " + (i+1);
                int secToWait = 2;//等待时间
                var t = new Thread(() => Eat(threadName,secToWait));
                t.Start();
            }

            //输出
//                Student 4 占了一个位置,准备吃饭
//                Student 2 占了一个位置,准备吃饭
//                Student 4 吃完饭了,让出位置
//                Student 3 占了一个位置,准备吃饭
//                Student 2 吃完饭了,让出位置
//                Student 5 占了一个位置,准备吃饭
//                Student 3 吃完饭了,让出位置
//                Student 1 占了一个位置,准备吃饭
//                Student 5 吃完饭了,让出位置
//                Student 1 吃完饭了,让出位置

        }
    }

借助信号量限制占座吃饭的并发个数,当两个线程在进行吃饭的时候,其他3个线程需要等待,直到之前的线程完成所做的事情(吃饭)之后并调用semaphore.Release()方法发出信号。


生产者消费者模式

线程池

为什么使用线程池

创建线程的昂贵的操作,为每一个短暂的异步操作创建线程会产生显著的开销。所以需要使用Pooling技术。

线程池可以成功的适用于任何需要大量短暂的开销大的资源的情形。事前分配一定的资源,放到资源池中,每次需要新的资源的时候从池子中获取,而不是新创建一个。当该资源并不需要再被使用的时候,将其回收到池子中。

注意点

  1. 保持线程中的操作都是非常短暂的。不要再线程池中放入长时间运行的操作,或者阻塞工作线程。
  2. 请注意线程池中的工作线程都是后台线程。这意味着当所有的前台线程(包括主程序线程)完成后,所有的后台线程将停止工作。

线程池的使用

//异步操作,等待线程池中的线程来调度
        static void AsyncOperation(object state)
        {
            Console.WriteLine("Operation State {0}",state != null ? state :"null" );
            Console.WriteLine("当前工作线程的线程id: {0}",Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(1000);  //挂起1s
        }
        /// <summary>
        /// 测试线程池的使用
        /// </summary>
        public static void TestPool()
        {
            const int x = 1;
            const int y = 1;
            const string lambdaState = "lambdaState2";
            //将方法排入队列以便执行。 此方法在有线程池线程变得可用时执行
            ThreadPool.QueueUserWorkItem(AsyncOperation);
            Thread.Sleep(1000);

            //放入队列中,等待线程池来执行这个异步操作,并传递一个参数
            ThreadPool.QueueUserWorkItem(AsyncOperation, "customState");
            //使用lambda表达式
            ThreadPool.QueueUserWorkItem(_ =>
            {
                Console.WriteLine("Operation State {0}",_ != null ? _ :"null" );
                Console.WriteLine("当前工作线程的线程id: {0}",Thread.CurrentThread.ManagedThreadId);
                //可以利用闭包的特效访问线程外的变量
                Console.WriteLine("x+y = {0}",x+y);
                Thread.Sleep(1000);  //挂起1s

            },"customState2");

            Thread.Sleep(5000);
            Console.WriteLine("主线程退出.");
t x = 1;
            const int y = 1;
            const string lambdaState = "lambdaState2";
            //将方法排入队列以便执行。 此方法在有线程池线程变得可用时执行
            ThreadPool.QueueUserWorkItem(AsyncOperation);
            Thread.Sleep(1000);

            //放入队列中,等待线程池来执行这个异步操作,并传递一个参数
            ThreadPool.QueueUserWorkItem(AsyncOperation, "customState");
            //使用lambda表达式
            ThreadPool.QueueUserWorkItem(_ =>
            {
                Console.WriteLine("Operation State {0}",_ != null ? _ :"null" );
                Console.WriteLine("当前工作线程的线程id: {0}",Thread.CurrentThread.ManagedThreadId);
                //可以利用闭包的特效访问线程外的变量
                Console.WriteLine("x+y = {0}",x+y);
                Thread.Sleep(1000);  //挂起1s

            },"customState2");

            Thread.Sleep(5000);
            Console.WriteLine("主线程退出.");
  • 27
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值