多线程、线程池、同步异步、并发并行

一、多线程

  • windows系统是一个多线程的操作系统。一个程序至少有一个进程,一个进程至少有一个线程。
  • 进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线程称为主线程。例如当我们创建一个C#控制台程序,程序的入口是Main()函数,Main()函数是始于一个主线程的。它的功能主要是产生新的线程和执行程序。
  • C#是一门支持多线程的编程语言,通过Thread类创建子线程,引入using System.Threading命名空间。 
  • 应用程序必须运行完所有的前台线程才可以退出;而对于后台线程,应用程序则可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束(默认:通过Thread类创建的线程为前台线程;线程池线程为后台线程(基于任务的异步操作在线程池线程自动执行,因此也默认基于后台线程))。
  • 线程函数最多只有一个object参数,不允许有返回值;参数通过MyThread.Start(参数)传入。

优点

1、 多线程可以提高CPU的利用率,因为当一个线程处于等待状态的时候,CPU会去执行另外的线程

2、 提高了CPU的利用率,就可以直接提高程序的整体执行速度

1、线程开的越多,内存占用越大;线程切换开销大

2、协调和管理代码的难度加大,需要CPU时间跟踪线程

3、线程之间对资源的共享可能会产生可不遇知的问题

 二、线程池

进程中创建大量线程,这些线程很多时间处于睡眠状态,等待事件发生。 通过线程池,可以通过向应用程序提供由系统管理的辅助线程池,从而更有效地使用线程。一个应用程序最多只能有一个线程池。

(即每新建一个线程都需要占用内存空间和其他资源,而新建了那么多线程,有很多在休眠,或者在等待资源释放;又有许多线程只是周期性的做一些小工作,如刷新数据等等。而线程池中的线程执行完指定的方法后并不会自动消除,而是以挂起状态返回线程池,如果应用程序再次向线程池发出请求,那么处以挂起状态的线程就会被激活并执行任务,而不会创建新线程,这就节约了很多开销。只有当线程数达到最大线程数量,系统才会自动销毁线程。因此,使用线程池可以避免大量的创建和销毁的开支,具有更好的性能和稳定性,其次,开发人员把线程交给系统管理,可以集中精力处理其他任务。)

ThreadPool是一个静态类,因此可以直接使用,不用创建对象。调用方法 QueueUserWorkItem将方法排队,以便在线程池线程上执行。 通过传递委托的方法执行此操作。可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。

ThreadPool.QueueUserWorkItem(ThreadProc);
ThreadPool.QueueUserWorkItem(ThreadProc, obj);

ThreadPool.QueueUserWorkItem(new WaitCallback(方法名));
ThreadPool.QueueUserWorkItem(new WaitCallback(方法名), 参数);
  • 不要将长时间运行的操作放进线程池中;
  • 不应该阻塞线程池中的线程;
  • 线程池中的线程都是后台线程(又称工作者线程);
  • 适合需要处理的任务的数量大且单个任务处理的时间比较短的场景;
  • 线程池最多管理线程数量=“处理器数 * 250”,最小不小于处理器数。

注意:当线程池重用线程时,它不会清除线程本地存储或用属性标记的 ThreadStaticAttribute 字段中的数据。 因此,当方法检查使用 ThreadStaticAttribute 特性标记的线程本地存储或字段时,它发现的值可能从之前使用的线程池线程中遗留下来。 

 三、同步、异步

异步和多线程都是线程的一种应用。

多线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往往由于使用多线程编程的简单和符合习惯,所以很多朋友往往会使用多线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处理大量的并发操作时就不合适了。 

  • 同步:从前到后、从上到下一步一步顺序执行,与直观相符。
  • 异步: 异步方法中,在遇到await之前程序按正常顺序执行,当遇到await时当前异步方法被挂起,特别注意仅是当前异步方法被挂起,与线程无关,不是当前线程被挂起!因此调用该异步方法的原方法可正常继续执行,【特别注意整个过程与线程无关,多线程只是实现异步的一种手段】当await等待的内容执行完毕(返回void、Task、Task<T>),当前程序执行点将回跳到该await阻塞的下一句,一直将异步方法执行完毕后再回到原调用方法刚才所执行到的位置处继续执行。(有点像子线程+回调函数,但注意Task执行于线程池线程,性能更好。)

await在控制异步执行的次序!

异步举例:你需要包饺子(将调用异步方法的原方法),待会要放盐,但是你发现没盐了,于是你叫跑腿小哥送盐(异步方法);在跑腿送来盐的过程中你会接着执行原方法,也就是洗韭菜、切猪肉、擀面皮......;当你正在擀面皮的时候跑腿到了,于是你把面团先放一边出门拿盐,并给小哥给了个好评;拿完盐后你回去接着擀面皮。(此处先不考虑面皮也擀完了就差盐了但是小哥还没送到的情况,这种情况下就需要调用Task.Wait()进行阻塞原方法以进行同步,因为没盐你就算所有都准备好了,接下来的时间内在包饺子这件事上你什么也干不了,只能等待。)

特别注意:

 按上述逻辑,程序执行到await时当前异步方法将被挂起,直到await语句执行结束;在此期间原调用方法在其自身所处的线程中继续执行,不受影响。但如此逻辑的前提是await等待的语句执行于其他线程,不然在await等待该语句的过程中,该线程被间接阻塞,原调用函数也被间接阻塞,整体退化为同步方法。如下面代码:

void Main()
{
    Console.WriteLine("First\n");
    AsyncMethod1();
    Console.WriteLine("Second\n");
    AsyncMethod2();
    Console.WriteLine("Thrid\n");

    Console.WriteLine("---------\n");
    Console.ReadKey();//阻塞主线程
}

void async AsyncMethod1()
{
    Console.WriteLine("AsyncMethod1 Enter\n");

    await Thread.Sleep(2000); 
    //await将挂起当前异步方法,等待此语句执行完毕,但由于此句与调用函数Main处于同一线程,因此线程被阻塞,Main被间接阻塞,整体退化为同步方法。

    Console.WriteLine("AsyncMethod1 Out\n");
}

void async AsyncMethod2()
{
    Console.WriteLine("AsyncMethod2 Enter\n");

    await Task.Run(()=>Thread.Sleep(2000));
    //await挂起当前异步方法并等待该语句执行完毕,由于该语句运行于其他线程,因此为正常异步逻辑

    Console.WriteLine("AsyncMethod2 Out\n");
}

最终输出:

First
AsyncMethod1 Enter
AsyncMethod1 Out
Second
AsyncMethod2 Enter
Third
------------------
AsyncMethod2 Out

另外注意,由于异步async、await与“可等待”的Task搭配使用,而Task是运行于线程池线程,属于后台线程,当前台线程结束后整个进程随之结束,后台线程将被强制结束。因此上述示例中若无Console.ReadKey()阻塞前台线程则打印完“-------”后整个进程结束,AsyncMethod2 Out不会被打印出来!

四、并发、并行 

  • 并发是指一个处理器通过划分时间片“同时”处理多个任务,逻辑上同时;
  • 并行是指多个处理器或者是多核的处理器同时处理多个不同的任务,物理上同时。

前者可以理解为多个事件在同一时间间隔内发生,后者为多个事件在同一时刻发生。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值