• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

《learning hard C#学习笔记》读书笔记(19)多线程

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

 19.1 多线程编程知识

19.1.1 进程与线程的概念

进程:
  1. 可以理解为一块包含某些资源的内存区域,操作系统通过进程方式把它工作划分为不同的单元。

  2. 一个应用程序可以对应多个进程。

线程:
  1. 线程是进程中的独立执行单元,操作系统调度线程来使应用程序工作。

  2. 一个进程中至少包含一个线程,称为主线程。

进程与线程的关系
线程是进程的执行单元,操作系统通过调度线程使应用程序工作。
进程是线程的容器,由操作系统创建,由在具体的执行过程中创建线程。
 

19.1.2 线程的调度

生活中吃饭的时候看电视,你需要来回切换这两个动作,他们由你来进行调度的。计算机里,线程相当于你的动作,操作系统相当于你,操作系统需要调度线程使他们轮流工作。
Windows是抢占式多线程系统。因为线程可以在任意事件里被抢占,来调度另一个线程。操作系统为每个线程分配了0~31某一优先级,优先级高的优先分配给CPU。
windows 支持7个相对线程优先级,Idle、Lowest、BelowNormal、Normal、AboveNormal、Highest、Time-Critical 。程序可以设置 Thread 的 Priority 属性来改变线程的优先级,该属性为 ThreadPriority 枚举类型,成员包含了 Lowest、BelowNormal、Normal、AboveNormal、Highest,CLR 为自己保留了 Idle、Time-Critical 

1、Time-critical:关键时间(最高的相对线程优先级)

2、Heightest:最高(翻译是这么翻译,但是并不是最高的相对线程优先级)

3、Above normal:高于标准

4、Normal:标准

5、Below normal:低于标准

6、Lowest:最低(翻译是这么翻译,但是并不是最低的相对线程优先级)

7、Idle:空闲

 

19.1.3 线程也分前后台

前台线程:只有所有的前台线程都关闭才能完成程序关闭。(主线程一直是前台线程)
后台线程:只要所有的前台线程都结束,后台线程自动结束(CLR会强制结束所有仍运行的后台线程,却不会抛出异常)。
  1. class Program  

  2. {  

  3.     static void Main(string[] args)  

  4.     {  

  5.         Thread backThread = new Thread(Worker);  

  6.         backThread.IsBackground = true;  

  7.         backThread.Start();  

  8.         Console.WriteLine( "从主线程中退出");   

  9.     }  

  10.   

  11.     public static void Worker()  

  12.     {  

  13.         Thread.Sleep(1000);  

  14.         Console.WriteLine("从后台线程退出");  

  15.     }  

  16. }  

 CLRT + F5 (不调试)效果图

 
上面代码通过 Thread 创建一个线程对象,设置 IsBackground 属性指明线程为后台线程。不设置 IsBackground  属性,则所创建的线程默认为前台线程。
接着,调用 Start 函数启动该线程,此时后台线程会执行 Worker 函数代码。
从前面分析中看出,该控制台有两个线程,一个运行 Main 函数的主线程,另一个运行 Worker 函数的后台线程。由于前台执行完毕后 CLR 会无条件终止后台线程运行,所以前面代码中若启动后台线程,则主线程将会继续运行。
主线程运行完  Console.WriteLine( "从主线程中退出") 语句就会退出。此时, CLR 发现主线程运行结束,则会终止后台线程,然后整个程序结束运行。所以 Worker 函数中的 Console.WriteLine("从后台线程退出") 语句将不会执行。

通过分析,按 CLRT + F5 运行程序,你将会看到如图效果。如果按 F5 你将会看到输出结果一闪而过,因为主线程退出后,整个应用程序也跟着退出,就关闭了控制台程序。

分析执行后台线程的方法

 

1.所创建线程默认为非后台线程,所以注释掉 //backThread.IsBackground = true;(这时候不是后台线程了)

 
 

2.使主线程在后台线程执行完毕后再执行,即使主线程进入睡眠,且睡眠时间比后台线程长。

 
 
 
3.通过调用主函数的 Join 函数方法,确保主线程会在后台线程执行结束后开始运行
 
 
以上代码调用 backThread.Start() 确保主线程会在后台线程结束后再运行。这种方式涉及
线程同步的概念:在某些情况下,需要两个线程同步运行。即一个线程必须等待另外一个线程结束之后才能运行
        //     System.Threading.ParameterizedThreadStart 委托,它表示此线程开始执行时要调用的方法。
        public Thread(ParameterizedThreadStart start);
        //     System.Threading.ThreadStart 委托,它表示此线程开始执行时要调用的方法。
        public Thread(ThreadStart start);
        public Thread(ParameterizedThreadStart start, int maxStackSize);  
 ParameterizedThreadStart 与 ThreadStart 的区别:ParameterizedThreadStart 可以有参数,ThreadStart 没有参数。本实例就是创建的ThreadStart。
 
 

19.2 线程的容器——线程池

通过 Thread 类手动创建线程的创建和销毁会耗费大量时间,这样的手动操作将造成性能的损失。因此,.NET引入了线程池机制。

19.2.1 线程池

 

  • 线程池是一个存放线程的地方,这种集中存放有利于线程的管理。

  • CLR初始化时,线程池中没有线程。(线程池内部维护了一个操作请求队列).

  • 执行异步操作时,需要调用 QueueUserWorkItem 方法将任务添加到线程池队列。线程池实现的代码会从队列中提取任务,并委派给线程池中的线程执行。

 

 

    1. 没有空闲的线程,线程池会创建一个新线程去执行提取的任务。

    2. 当线程池完成了某个任务,线程不会销毁,而是返回到线程池中,等待下一个请求。(由于不销毁,所以不会产生性能损失)

 

 

  • 线程池创建的是后台线程,优先级是 Normal 。        

 

 

19.2.2 通过线程池来实现多线程

使用线程池的线程,要调用静态方法 ThreadPool.QueueUserWorkItem 以指定线程要调用的方法。该静态方法有两种:

public static bool QueueUserWorkItem(WaitCallback callBack);  

public static bool QueueUserWorkItem(WaitCallback callBack, object state);  

 

  • 这两个方法用于向线程池队列添加一个工作项(work item)以及一个可选的状态数据。

  • 工作项是指一个由 callback 参数标识的委托对象,被委托对象包装的回调方法由线程池来执行。

 

 

 

  • 传入的回调方法匹配 System.Threading.WaitCallback 委托类型

 

  1. static void Main(string[] args)  

  2. {  

  3.     Console.WriteLine("主线程ID={0}",Thread.CurrentThread.ManagedThreadId);  

  4.     ThreadPool.QueueUserWorkItem(CallbackWorkItem);  

  5.     ThreadPool.QueueUserWorkItem(CallbackWorkItem, "work");  

  6.     Thread.Sleep(3000);  

  7.     Console.WriteLine("主线程退出");  

  8. }  

  9.   

  10. private static void CallbackWorkItem(object state)  

  11. {  

  12.     Console.WriteLine("线程池开始执行");  

  13.     if (state !=null)  

  14.     {  

  15.         Console.WriteLine("线程池线程ID = {0} 传入的参数为 {1}",Thread.CurrentThread.ManagedThreadId,state.ToString());  

  16.     }  

  17.     else  

  18.     {  

  19.         Console.WriteLine("线程池线程ID ={0}",Thread.CurrentThread.ManagedThreadId);  

  20.     }  

  21. }  

 

19.2.3 协作式取消线程池线程

.NET Framework 提供了取消操作的模式,这个模式是协作式的。为了取消操作,我们必须创建一个 System.Threading.CancellationTokenSource 对象。

  1. static void Main(string[] args)  

  2. {  

  3.     Console.WriteLine("主线程运行");  

  4.     CancellationTokenSource cts = new CancellationTokenSource();  

  5.     ThreadPool.QueueUserWorkItem(callback, cts.Token);  

  6.     Console.WriteLine("按下回车键来取消操作");  

  7.     Console.Read();  

  8.     cts.Cancel();//取消请求  

  9.     Console.ReadKey();  

  10. }  

  11.   

  12. private static void callback(object state)  

  13. {  

  14.     CancellationToken token = (CancellationToken) state;  

  15.     Console.WriteLine("开始计数");  

  16.     Count(token,1000);//开始计数  

  17. }  

  18.   

  19. private static void Count(CancellationToken token, int countto)  

  20. {  

  21.     for (int i = 0; i < countto; i++)  

  22.     {  

  23.         if (token.IsCancellationRequested)  

  24.         {  

  25.             Console.WriteLine("计数取消");  

  26.             return;  

  27.         }  

  28.         Console.WriteLine("计数为:" + i);  

  29.         Thread.Sleep(300);  

  30.     }  

  31.     Console.WriteLine("计数完成");  

  32. }  

首先创建一个 CancellationTokenSource 实例,将该实例作为参数传入QueueUserWorkItem 方法。线程池会创建一个线程池线程,运行该方法传入的回调函数 callback  ,并在 callback 中执行 Count 函数来计数。

 Count 函数中检查 CancellationTokenSource 类实例的状态。当用户按回车键时,该实例的 IsCancellationRequested 属性将返回 ture 。因此退出 Count 方法。否则一直运行。

 

19.3 线程同步

 多线程中,为了保证后者线程,只有等待前者线程完成之后才能继续执行。好比排队买票,前面的人没买票之前,后面的人必须等待。

19.3.1 多线程程序中存在的隐患

多线程可以提高程序的性能和用户体验。然而当我们创建多个线程后,它们可能同时访问某一个共享资源,这将损坏资源中所保存的数据。这时候我们需要使用线程同步,确保某一时刻只有一个线程在操作共享资源。

举例来说,火车票销售系统允许多人同时购买,因此该系统肯定采用了多线程技术。但由于系统中有多个线程在对同一资源(火车票)进行操作,我们必须确保只有其他线程执行结束后,新的线程才开始执行。这样可以避免多位顾客买到同一张票。

  1. private static int tickets = 100;//100张票  

  2.   static void Main(string[] args)  

  3.   {  

  4.       Thread thread1 = new Thread(SaleTicketThread1);  

  5.       Thread thread2 = new Thread(SaleTicketThread2);  

  6.       thread1.Start();  

  7.       thread2.Start();  

  8.       Thread.Sleep(4000);  

  9.   }  

  10.   

  11.   private static void SaleTicketThread1()  

  12.   {  

  13.       while (true)  

  14.       {  

  15.           if (tickets>0)  

  16.           {  

  17.               Console.WriteLine("线程1出票:"+tickets--);  

  18.           }  

  19.           else  

  20.               break;  

  21.       }  

  22.   }  

  23.   private static void


    鲜花

    握手

    雷人

    路过

    鸡蛋
    该文章已有0人参与评论

    请发表评论

    全部评论

    专题导读
    上一篇:
    vscode配置c/c++开发环境发布时间:2022-07-13
    下一篇:
    【转】C语言 统计整数二进制表示中1的个数发布时间:2022-07-13
    热门推荐
    阅读排行榜

    扫描微信二维码

    查看手机版网站

    随时了解更新最新资讯

    139-2527-9053

    在线客服(服务时间 9:00~18:00)

    在线QQ客服
    地址:深圳市南山区西丽大学城创智工业园
    电邮:jeky_zhao#qq.com
    移动电话:139-2527-9053

    Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap