多线程编程01

    科技2022-08-14  93

    多线程编程01

    线程概念

    线程可以被描述为它所处的进程中的一个微进程,它拥有起点,执行的顺序系列和一个终点。

    线程是在进程的内部执行的指令序列,由进程负责管理和调度

    在进程内的每个线程共享相同的内存空间和数据资源

    进程中的每个线程共享代码区,即不同的线程可以执行同样的函数

    即线程组成进程

    线程和进程的联系:

    进程拥有自己独立的内存空间和数据,进程内的所有线程是共享内存空间和数据的。

    进程对应着一段程序,它是由一些在同一个程序里面独立的同时的运行的线程组成的。

    线程的运行依赖与进程提供的上下文环境,并且使用的是进程的资源。

    进程是系统进行资源分配的基本单位。

    线程是操作系统CPU时间的基本单元,是系统中最小的执行单元。

    多线程

    通常指的是多线程编程

    是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

    好处:

    可以提高CPU的利用率

    在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率

    缺点:

    多线程本身可能影响系统性能的不利方面

    线程也是程序,所以线程需要占用内存,线程越多占用内存也越多

    多线程需要协调和管理,所以需要CPU时间跟踪线程

    线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题

    线程太多会导致控制太复杂,容易出现造成很多Bug又不容易排除的情况

    线程的调度

    1) 每一个线程的优先级是0到31。高优先级的线程ready之后,不管低优先级的线程在做什么,立即上位,没话说。Windows会把最高优先级的不同线程调度到各个CPU上并行执行,多核多处理器谁也不闲着。

    2) Windows制定进程有6个优先等级,线程有7个,通过组合来得出实际的线程优先级0到30(0优先级保留给Windows用于内存释放)。CLR保留了线程优先级中的最低和最高级,供程序员可设置的只有5个等级。

    3) 进程的优先级是一个虚拟的概念,只是为了帮助用于映射到1-31中的某个等级,一般来说进程的等级默认为创建它的进程的等级。很多进程都是Windows Explorer创建的,默认也就是Nomral这个等级,说白了我们的线程在大多情况下映射到Windows线程优先级为6-10。

    为什么说线程是比较昂贵的?

    •1)从内存上来说,(对于32位架构)每一个线程包含线程内核对象(700字节)/线程环境块(4KB)/内核堆栈(12KB)/用户堆栈(1MB)。并且可以发现,这1MB的用户堆栈内存在CLR线程创建的时候完全分配,并不是动态增加的(Windows线程的创建只是保留1MB的内存空间)。

    •2)从线程切换上来说,需要做哪些步骤来进行切换?首先是把CPU寄存器中的值保存到当前线程的内核对象中,然后如果线程切换到不同CPU的话需要为CPU准备新的虚拟地址空间,最后把目标线程内核对象中寄存器的值复制到CPU寄存器中。

    •3) 更大的性能损害来自于,线程切换之后缓存中的数据可能会不能命中,需要重新准备这些数据。

    •4) 此外,在垃圾回收的时候,CLR会挂起所有线程,查看线程堆栈,垃圾回收压缩后重置堆栈指针地址。

    多线程和多进程面临的问题

    •并发

    指多个进程同时执行(单CPU里其实是轮询执行),但其实多个进程共享的是同一个CPU、内存及IO设备,由此产生了资源竞争的情况。为解决这样的情况,才出现了同步和互斥。也可以说是并发问题引出了同步和互斥技术。——同时执行

    •同步

    多个进程间相互依赖,也就是说B进程要执行的条件是A进程执行完后输出相应结果,B进程得到A进程的运行结果后才能顺利执行,这种A等待B的情况叫同步。——有依赖关系

    •死锁

    当A进程在等B进程释放资源(或锁),B进程又同时需要等待某个资源的释放,这个资源又刚好被A进程占有,这样就引发了死锁。——两个进程互相等待

    •饥饿

    当A进程等待B进程释放资源(或锁),B进程在释放资源(或锁)前死掉了,这样A进程就处于一直等待的情况,这样引发了A进程的饥饿问题。

    同步异步demo:

    using System; using System.Runtime.InteropServices.ComTypes; using System.Windows.Forms; namespace day30test03 { class Program { /// <summary> /// 同步异步调用 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Console.WriteLine($"主线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}{System.Threading.Thread.CurrentThread.Priority}{System.Threading.Thread.CurrentThread.ThreadState}{System.DateTime.Now}"); Console.WriteLine("同步"); //MessageBox.Show("1"); //Console.ReadLine(); //全部都由主线程执行,所以每一遍打印都是上一次完成之后才能进行 Work work = new Work(); for(int i = 0; i < 10; i++) { work.show(); } Console.WriteLine("ok"); Console.WriteLine("异步"); //每次都创建一个新的子线程来执行打印 for(int j = 0; j < 10; j++) { Work work1 = new Work(); work1.Sayshow(); } } } class Work { //定义私有字段的线程 private System.Threading.Thread t; //C#内定义好的一个无参数无返回值的委托 private System.Threading.ThreadStart ts; public void show() { Console.WriteLine($"子线程:{System.Threading.Thread.CurrentThread.ManagedThreadId} {System.Threading.Thread.CurrentThread.Priority} {System.Threading.Thread.CurrentThread.ThreadState} {System.DateTime.Now}"); System.Threading.Thread.Sleep(10); } public void Sayshow() { //委托,将方法作为参数传入 this.ts = new System.Threading.ThreadStart(this.dowork); //线程的构造方法,需要传递一个委托,线程启动时会调用委托执行方法 this.t = new System.Threading.Thread(ts); this.t.Priority = System.Threading.ThreadPriority.Highest; this.t.Name = "Sayshow thread"; this.t.IsBackground = true; this.t.Start(); } public void dowork() { Console.WriteLine($"子线程:{System.Threading.Thread.CurrentThread.ManagedThreadId} {System.Threading.Thread.CurrentThread.Priority} {System.Threading.Thread.CurrentThread.ThreadState} {System.DateTime.Now} "); System.Threading.Thread.Sleep(10); } } }

    多线程的传参问题:

    1.静态字段传递

    安全性不够,缺点很多

    using System; using System.Runtime.InteropServices.ComTypes; namespace day30test05 { class Program { [ThreadStatic] //声明这是线程调用的静态字段 尽可能的避免数据争用 public static String str = "111静态传参"; /// <summary> /// 多线程的传参问题 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Console.WriteLine($"主线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}{System.Threading.Thread.CurrentThread.Priority}{System.Threading.Thread.CurrentThread.ThreadState}{System.DateTime.Now}"); //Console.WriteLine("同步"); //MessageBox.Show("1"); //Console.ReadLine(); /*Work work = new Work(); for (int i = 0; i < 10; i++) { work.show(); } Console.WriteLine("ok"); Console.WriteLine("异步"); for (int j = 0; j < 10; j++) { Work work1 = new Work(); work1.Sayshow(); }*/ } } class Work { private System.Threading.Thread t; private System.Threading.ThreadStart ts; public void show() { Console.WriteLine($"子线程:{System.Threading.Thread.CurrentThread.ManagedThreadId} {System.Threading.Thread.CurrentThread.Priority} {System.Threading.Thread.CurrentThread.ThreadState} {System.DateTime.Now}"); System.Threading.Thread.Sleep(10); } public void Sayshow() { this.ts = new System.Threading.ThreadStart(this.dowork); this.t = new System.Threading.Thread(ts); this.t.Priority = System.Threading.ThreadPriority.Highest; this.t.Name = "Sayshow thread"; this.t.IsBackground = true; this.t.Start(); } public void dowork() { Console.WriteLine($"子线程:{System.Threading.Thread.CurrentThread.ManagedThreadId} {System.Threading.Thread.CurrentThread.Priority} {System.Threading.Thread.CurrentThread.ThreadState} {System.DateTime.Now} "); System.Threading.Thread.Sleep(10); } } }

    2.委托带参数传递

    较安全,灵活性不够

    using System; namespace day30test06 { class Program { /// <summary> /// 多线程委托传递参数 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Test test = new Test(); test.Sayshow("1多线程委托传递参数"); Console.WriteLine("Hello World!"); } } class Test { private System.Threading.Thread t; //private System.Threading.ThreadStart ts; private System.Threading.ParameterizedThreadStart tps; public void Sayshow(String str) { //this.ts = new System.Threading.ThreadStart(this.dowork); this.tps = new System.Threading.ParameterizedThreadStart(this.dowork); this.t = new System.Threading.Thread(tps); this.t.Priority = System.Threading.ThreadPriority.Highest; this.t.Name = "Sayshow thread"; this.t.IsBackground = true; this.t.Start(str); } public void dowork(Object obj) { Console.WriteLine(obj); Console.WriteLine($"子线程:{System.Threading.Thread.CurrentThread.ManagedThreadId} {System.Threading.Thread.CurrentThread.Priority} {System.Threading.Thread.CurrentThread.ThreadState} {System.DateTime.Now} "); System.Threading.Thread.Sleep(10); } } }

    3.对象封装传参

    灵活性好,安全性较好,

    using System; namespace day30test08 { class Program { /// <summary> /// 对象封装传递参数 /// </summary> /// <param name="args"></param> static void Main(string[] args) { for (int i = 0; i < 10; i++) { Test test = new Test(); test.Sayshow(new FUZhu("对象封装传递参数")); } Console.WriteLine("Hello World!"); Console.ReadLine(); } } class Test { private System.Threading.Thread t; private System.Threading.ThreadStart ts; //private System.Threading.ParameterizedThreadStart tps; private object obj; public void Sayshow(object oj) { this.ts = new System.Threading.ThreadStart(this.dowork); this.t = new System.Threading.Thread(this.ts); this.t.Priority = System.Threading.ThreadPriority.Highest; this.t.Name = "Sayshow thread"; this.t.IsBackground = true; this.obj = oj; this.t.Start(); } public void dowork() { //Console.WriteLine(obj); //Console.WriteLine(1); Console.WriteLine($"子线程:{System.Threading.Thread.CurrentThread.ManagedThreadId} {System.Threading.Thread.CurrentThread.Priority} {System.Threading.Thread.CurrentThread.ThreadState} {System.DateTime.Now} {(this.obj as FUZhu).Msg}"); //System.Threading.Thread.Sleep(1000); //Console.WriteLine(2); } } class FUZhu { private String msg; public FUZhu(String str) { this.msg = str; } public string Msg { get => msg; set => msg = value; } } }

    疑问:

    同样的代码,再.net core和.net framework中的实现却不一样,core实现不了framework的效果,可能是core对多线程的调度不一样。

    委托异步以多线程的区别:

    委托启动之后线程不能停止,无法被干涉,而多线程启动之后线程可被很好的控制。

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace day30test09 { class Program { /// <summary> /// 多线程和委托的区别 /// </summary> /// <param name="args"></param> static void Main(string[] args) { for (int i = 0; i < 10; i++) { Test test = new Test(); //委托使用多线程 System.Threading.ThreadStart ts = new System.Threading.ThreadStart(test.dowork); ts.BeginInvoke(null, null); } Console.Read(); } } class Test { private System.Threading.Thread t; private System.Threading.ThreadStart ts; //private System.Threading.ParameterizedThreadStart tps; private object obj; public void Sayshow(object oj) { this.ts = new System.Threading.ThreadStart(this.dowork); this.t = new System.Threading.Thread(this.ts); this.t.Priority = System.Threading.ThreadPriority.Highest; this.t.Name = "Sayshow thread"; this.t.IsBackground = true; this.obj = oj; this.t.Start(); } public void dowork() { //Console.WriteLine(obj); //Console.WriteLine(1); Console.WriteLine($"子线程:{System.Threading.Thread.CurrentThread.ManagedThreadId} {System.Threading.Thread.CurrentThread.Priority} {System.Threading.Thread.CurrentThread.ThreadState} {System.DateTime.Now}"); //System.Threading.Thread.Sleep(1000); //Console.WriteLine(2); } } }

    Thread类的属性和方法

    属 性说 明CurrentThread静态属性,获取当前正在运行的线程IsAlive获取一个值,该值指示当前线程的执行状态IsBackground获取或设置一个值,该值指示是否是后台线程Name获取或设置线程的名称Priority获取或设置一个值,该值指示线程的调度优先级ThreadState获取一个值,该值包含当前线程的状态方 法说 明Start开始执行线程Abort终止线程Join阻止调用线程,直到被调用线程终止为止,它在被调用线程实际停止执行之前或可选超时间隔结束之前不会返回Interrupt打断处于WaitSleepJoin线程状态的线程,使其继续执行Sleep静态方法,使当前线程停止指定的毫秒数

    ThreadState枚举的成员

    Running,线程已启动,正在运行中。

    Stopped,线程已停止。

    Unstarted,尚未对线程调用Thread.Start方法。

    WaitSleepJoin,由于调用Sleep或Join,线程已暂停。

    Aborted,线程状态包括 AbortRequested 并且该线程现在已死,但其状态尚未更改为 Stopped

    AbortRequested,已对线程调用了Thread.Abort 方法后的挂起状态。

    线程方法和状态之间的关系

    前提条件结果线程****A线程****B线程A状态线程B状态A线程被创建但未调用Start****方法Unstarted对A调用****A.Start()Running调用 Thread.Sleep()WaitSleepJoinA进入running状态之后,对B****调用 B.Join()由Running变为 WaitSleepJoinRunning对A调用A. Interrupt()由WaitSleepJoin 变为RunningA.AbortStoppedRunning

    Join方法

    Join()

    调用线程等待被调用线程完成所有操作后,当前线程才能继续执行

    Join(int time)

    调用线程等待被调用线程完成所有操作后或者等待时间超过指定时间time以后,当前线程才能继续执行

    CPU时间片

    什么是CPU时间片

    线程靠抢CPU时间片而执行,谁抢的多谁利用CPU的时间就多也就执行得快

    优先级

    决定了这个争抢能力

    线程优先级高的在同一时间越能获得CPU时间片

    优先级枚举

    ØThreadPriority

    成员

    Normal,默认情况下创建线程的优先级 ,原则上相同优先级的线程会获得相同的CPU时间。

    AboveNormal,处于Normal优先级之上但低于Highest优先级。

    BelowNormal,处于Normal优先级之下但高于Lowest优先级。

    Highest,最高的优先级。

    Lowest,低于BelowNormal的最低优先级。

    线程同步

    使线程协调一致的工作,最简单的额锁定方式——lock

    using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace day30test10 { class Program { /// <summary> /// 线程同步 /// 使线程协调一致的工作 /// 最简单的额锁定方式——lock /// </summary> /// <param name="args"></param> static void Main(string[] args) { Test tes = new Test(); for(int i = 0; i < 10; i++) { System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(tes.gets)); t.Start(); System.Threading.Thread t1 = new System.Threading.Thread(new System.Threading.ThreadStart(tes.gets1)); t1.Start(); System.Threading.Thread t2 = new System.Threading.Thread(new System.Threading.ThreadStart(tes.gets2)); t2.Start(); } } } class Test { System.Collections.Hashtable ht = new System.Collections.Hashtable(); int count; int[] counts; public int Count { get => count; set => count = value; } public int[] Counts { get => counts; set => counts = value; } public Hashtable Ht { get => ht; set => ht = value; } public Hashtable Ht1 { get { lock(this) { return ht1; } } set { lock (this) { ht1 = value; } } } public Hashtable Ht2 { get { if(this.ht2.IsSynchronized == false) { return ht2.SyncRoot as System.Collections.Hashtable; } else { return ht2; } } set { lock (this) { ht2 = value; } } } //没有使用任何的线程同步的操作 public void gets() { System.Collections.Hashtable Ht1 = this.Ht; } System.Collections.Hashtable ht1 = new System.Collections.Hashtable(); //使用lock的方式处理线程同步 public void gets1() { System.Collections.Hashtable Ht1 = this.Ht; } System.Collections.Hashtable ht2 = new System.Collections.Hashtable(); //使用所有集合都有的IsSynchronized和SyncRoot属性来处理,最佳方式 public void gets2() { System.Collections.Hashtable Ht1 = this.Ht; } } }

    Monitor监视器

    用于代码块的锁定(较安全)

    类似使用Lock关键字(更轻量,方便)

    Processed: 0.009, SQL: 8