C# 多线程技术

2022-03-29 09:34:54 浏览数 (1)

这节讲一下多线程(Thread)技术。

在讲线程之前,先区分一下程序,进程,线程三者的区别,大体上说,一个程序可以分为多个进程,一个进程至少由一个线程去执行,它们是层层包含的关系。我们写的程序,就是一个进程,多个进程,以完成一个用户服务,或者完成一个大的界面展现,就组成一个程序,但在CPU层面,只有线程的概念,线程是最小的执行单位,Windows中采用CPU轮换制度,CPU给每个要执行的线程分配操作时间,轮流执行,但因为CPU的主频实在是太高,我们感受不到每个程序轮空期卡顿。

一个进程,开了一条线程去执行,那么这个线程就是主线程,一般在UI程序中,如果主线程执行CPU密集型的耗时工作(如IO操作),那么就会导致界面处于”假死“状态,直到主线程完成这个耗时的任务,所以,我们需要解决这种假死的问题,以带给用户更好的交互体验,那么就要用到多线程技术,将耗时的工作,交给后台线程执行。

创建线程

使用Thread类创建线程,该类位于System.Thread类之下,必须在创建之时给填入执行方法,或者填入lambda表达式。

代码语言:javascript复制
Thread thread=new Thread(() =>
    {
        for (int i = 0; i <= 100; i  )
        {
            Console.WriteLine($"{Thread.CurrentThread.Name}=========>{i}");
        }
    });

以上是创建了一个线程,并填入一个lambda表达式,输出当前线程的名称和0-100,注意,创建并不是启动,启动需要调用Start()方法。

下面看一段完整的代码:

代码语言:javascript复制
Thread thread=new Thread(() =>
    {
        Console.WriteLine(Thread.CurrentThread.IsAlive);
        for (int i = 0; i <= 100; i  )
        {
            Console.WriteLine($"{Thread.CurrentThread.Name}=========>{i}");
        }
    });
thread.Name = "BackgroundThread";
thread.IsBackground = true;
thread.Start();
for (int i = 0; i <= 100; i  )
{
    Console.WriteLine($"main=========>{i}");
}

Console.WriteLine(thread.ManagedThreadId);
Thread.Sleep(200);
Console.WriteLine(thread.IsAlive);

先介绍一下代码中出现的几个属性和方法:

Name属性,故名思意,这是为线程起一个名字,

IsBackground属性,设置线程是否是后台线程,如果前台线程也就是主线程结束运行,它所有的后台线程也会立即终止。

IsAlive只读bool属性,标识当前线程是否执行完毕,这里要说一下线程的生命周期,它的声明周期是从开始执行到结束,执行完交给线程的代码线程立即dead掉,也可以强行挂起线程,但这个方法已被舍弃,因为强行挂起线程有弊端,就像在高速上跑的小汽车,不能随便就直接拦截。

ManagedThreadId属性,获取当前线程ID

Thread.CurrentThread属性,获取当前的线程。

Thread.Sleep(毫秒值)方法,执行到此方法,线程会睡眠,哪个线程执行,哪个线程睡,此处让其睡200毫秒,为了展示IsAlive属性。

Start()方法,是让这个线程启动。

接下来看一下运行结果(为了方便查看结果,我把循环都调成了6次):

可以看到,执行中的IsAlive属性是true,睡了200秒,线程执行完毕,IsAlive属性变为flase。

Join方法

线程调用join()方法,是指示CPU该线程交出自己的执行权(也就是该线程处于阻塞状态),直到其它线程执行执行完毕,Join()方法有个毫秒值重载,用于设置交出执行权多少时间。将上方代码更改后:

代码语言:javascript复制
 Thread thread=new Thread(() =>
    {
        for (int i = 0; i <= 20; i  )
        {
            Console.WriteLine($"{Thread.CurrentThread.Name}=========>{i}");
            if (i == 10)
            {
                Thread.CurrentThread.Join(200);
            }
        }
    });
    thread.Name = "BackgroundThread";
    thread.IsBackground = true;
    thread.Start(); 
  
    for (int i = 0; i <= 20; i  )
    {
        Console.WriteLine($"main=========>{i}");
    }
    Thread.Sleep(200);

执行结果为:

当在i=10的时候,后台线程交出了执行权200毫秒,这期间只有主线程在工作。

线程的优先级

线程的优先级是可以设置的,但是,这仅仅是人为了提高了线程的优先级,至于真正的调配还得看CPU,所以一般多线程开发,是很繁琐的事情,维护起来也困难,所以多线程技术需要慎用,不能滥用。

线程优先级有个枚举类,源码如下:

代码语言:javascript复制
public enum ThreadPriority
{
    Lowest,//优先级最低
    BelowNormal,//低于正常
    Normal,//正常
    AboveNormal,//较高
    Highest,//最高
  }

当我将其优先级更改为最高时,也并不能决定它是最快执行的,所以优先级的设置只是理论上的。

线程池

线程池是系统事先创建好的一堆后台线程,当一个程序需要一个后台线程执行一个不太重要的线程,并且代码简短的话,可以使用线程池,不用再自己new一个线程,这能略微提高性能。

代码语言:javascript复制
ThreadPool.QueueUserWorkItem(s => Console.WriteLine(s),"helloworld");

使用 ThreadPool.QueueUserWorkItem()创建一个线程池线程,当线程池中有空闲线程时会取出一个线程来执行。须注意的是,第一个参数是一个带有一个参数的委托WaitCallback,第二个参数会作为这个委托的参数传入。

代码语言:javascript复制
public delegate void WaitCallback(object state);

线程安全

不得不提的是,多线程存在线程安全问题,所以在开发时要注意。何为线程安全呢,举个例子,火车站售票,1000个人同时抢100张票,多线程情况下,有可能两个甚至多个线程同时抢到一个票,最后这几个线程都是一个座号,这显然是不合情理的。解决线程安全问题,就必须保证共享数据的同步性,也就是说同一时间只有一个线程访问共享的数据,关于线程安全的例子,我会在下一期进行讲解。

本节到此结束...

0 人点赞