C# 线程小结
进程与线程
什么是进程?
当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。
而一个进程又是由多个线程所组成的。
什么是线程?
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
进程与线程的比较:
线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,也是程序执行流的最小单元,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
1) 调度。在传统的操作系统中,拥有资源和独立调度的基本单位都是进程。在引入线程的操作系统中,线程是独立调度的基本单位,进程是资源拥有的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同进程中进行线程切换,如从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。
2) 拥有资源。不论是传统操作系统还是设有线程的操作系统,进程都是拥有资源的基本单位,而线程不拥有系统资源(也有一点必不可少的资源),但线程可以访问其隶属进程的系统资源。
3) 并发性。在引入线程的操作系统中,不仅进程之间可以并发执行,而且多个线程之间也可以并发执行,从而使操作系统具有更好的并发性,提高了系统的吞吐量。
4) 系统开销。由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、 I/O设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程CPU环境的保存及新调度到进程CPU环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,由于同一进程内的多个线程共享进程的地址空间,因此,这些线程之间的同步与通信非常容易实现,甚至无需操作系统的干预。
5) 地址空间和其他资源(如打开的文件):进程的地址空间之间互相独立,同一进程的各线程间共享进程的资源,某进程内的线程对于其他进程不可见。
6) 通信方面:进程间通信(IPC)需要进程同步和互斥手段的辅助,以保证数据的一致性,而线程间可以直接读/写进程数据段(如全局变量)来进行通信。
什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
多线程的优点:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
多线程的缺点:
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
线程太多会导致控制太复杂,最终可能造成很多Bug;
1.任何程序在执行时,至少有一个主线程。
2.firstThread.Start()启动了一个线程后,用firstThread.Join()这个方法加入一个线程[即:暂停了主线程的运行],那么操作系统就会马上执行这个新加入的线程
3.Join就是加入的意思,也就是说新创建的线程加入到进程中,并马上执行
4.所以要想一个线程在启动后就马上执行,必须调用Thread.Join()方法.
5.到这里,Thread.Join()这个方法的作用也就明显了:当调用了 Thread.Join()方法后,当前线程会立即被执行,其他所有的线程会被暂停执行。当这个线程执行完后,其他线程才会继续执行。
线程的优先级
当线程之间争夺CPU时间时,CPU 是按照线程的优先级给予服务的。
在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,
在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。
1.主线程永远是最先执行的,但当新线程启动[Thread.Start()],并加入[Thread.Join()]后,主线程会暂停。
每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。
C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。
在C#中,关键字lock定义如下:
lock(expression)
statement_block
expression代表你希望跟踪的对象,通常是对象引用。
如果你想保护一个类的实例,一般地,你可以使用this;
如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。
而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
带参数的多线程调用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#region 执行带一个参数的多线程 Thread mythread = new Thread(new ParameterizedThreadStart(Calculate)); mythread.IsBackground = true; mythread.Start(500); #endregion private void Calculate(object Max) //带一个参数的委托函数 { int max = (int)Max; Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { Thread.Sleep(5); } stopwatch.Stop(); long lSearchTime = stopwatch.ElapsedMilliseconds; MessageBox.Show(lSearchTime.ToString() + "毫秒"); }
方式一: 定义一个类,将要传的参数设置为类的属性,然后将参数值赋值给类的属性,将类作为一个参数进行传达,以下代码通过两个参数示例,多个参数一样,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class MyClass { public int Max { get; set; } public int Min { get; set; } } #region 第一种方式:执行带多个参数的多线程 MyClass model = new MyClass(); model.Max = 500; model.Min = 0; Thread mythread1 = new Thread(new ParameterizedThreadStart(CalculateTwo)); mythread1.IsBackground = true; mythread1.Start(model); #endregion private void CalculateTwo(object Myclass) //带多个参数的委托函数 { MyClass model = (MyClass)Myclass; Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = model.Min; i < model.Max; i++) { Thread.Sleep(5); } stopwatch.Stop(); long lSearchTime = stopwatch.ElapsedMilliseconds; MessageBox.Show(lSearchTime.ToString() + "毫秒"); }
方式二:lambda表达式的方式,简单方便,
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#region 第二种方式:执行带多个参数的多线程 Thread mythread2 = new Thread(() => CalculateThree(500, 0)); mythread2.IsBackground = true; //設置為後臺線程,程式關閉后進程也關閉,如果不設置true,則程式關閉,此線程還在內存,不會關閉 mythread2.Start(); #endregion private void CalculateThree(int Max,int Min) //带多个参数的委托函数 { Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = Min; i < Max; i++) { Thread.Sleep(5); } stopwatch.Stop(); long lSearchTime = stopwatch.ElapsedMilliseconds; MessageBox.Show(lSearchTime.ToString() + "毫秒"); }
静态变量和静态方法的线程安全问题
静态方法内部创建的局部参数是线程安全的,不同线程调用同一个静态方法时,他们不会共享静态方法内部创建的参数,代码举例如下
public static void Test()
{
int i = 0;
Console.WriteLine(i);
i++;
}
上面代码中,变量i在不同线程间是不会共享的,不同线程分别调用该方法,输出都是1,。
静态变量在不同线程间是共享的,这个相信大家都知道,而当静态方法中对静态变量进行操作时,这就涉及到了线程安全问题:
private static int i = 0;
public static void Test()
{
Console.WriteLine(i);
i++;
}
这段代码在由不同线程进行操作时,存在线程安全问题,所以应该加锁
private static int i = 0;
public static void Test()
{
lock(this)
{
Console.WriteLine(i);
i++;
}
}
ui线程
在.net中使用多线程的朋友应该都知道在非ui线程是不能直接操作ui对象的,只能通过委托的方式将需要执行的代码发给UI线程,让ui线程代为执行。
那么这种设定背后的逻辑是什么?为什么ui对象只能由创建ui对象的线程进行操作?如果没有这样的限制会导致什么后果?
就是因为「它是以非多线程安全的模式来设计的」。
换来的是更低的复杂度和更高的性能。
所有Windows程序,其UI线程都是一个循环,不停地检测消息和分派消息,这样只需要读写消息的一个地方加锁。
没有限制导致什么后果?
你说多线程使用不安全的对象有什么后果?
多线程本身就存在并发问题,而单线程不存在该问题。解决这个问题,如果非ui线程要操作控件则需要委托ui线程执行。这也是为了在正确执行的前提下,有效降低编程难度的一种手段。否则在多线程操作ui控件下,要自己去维护ui控件各属性状态的一致性需要加很多锁,不便于编码