首页 > 其他分享 >多线程(进阶篇&小白易懂版)

多线程(进阶篇&小白易懂版)

时间:2024-04-10 22:05:22浏览次数:28  
标签:Thread void System new 进阶篇 线程 易懂 using 多线程

文章目录

多线程

概念:多线程就是多个线程同时工作的过程,我们可以将线程看作是程序的执行路径,每个线程都定义了一个独特的控制流,用来完成特定的任务。如果您的应用程序涉及到复杂且耗时的操作,那么使用多线程来执行是非常有益的。使用多线程可以节省 CPU 资源,同时提高应用程序的执行效率,例如现代操作系统对并发编程的实现就用到了多线程。

本篇为多线程进阶篇,使用C#语言,用winform辅助演示。多线程的基础(如线程生命周期,线程的属性和方法等)在本人C#博客有具体讲解
直达链接link

在这里插入图片描述

为什么要有多线程

使用场景:当你有一些可能会阻塞线程(如IO操作、网络请求、复杂计算等)的操作时,可以将它们封装为线程,并使用Thread类创建子线程来异步执行它们。这样可以避免阻塞UI线程或其他重要线程,提高应用程序的响应性和性能。

具体举例比如本段代码中卖烧饼,若一名顾客要求烧饼不带芝麻,店内只有老板一个,老板骂骂咧咧得挑芝麻,此时有人再来买烧饼,只能老板挑完后才能做烧饼,这可以理解为单线程

比如店内还有一名员工,老板则可以把这SB要求交个员工完成,直接为下名顾客服务,这可以理解为多线程。

请添加图片描述

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _01_多线程初始
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            //
            Thread thread = Thread.CurrentThread;
            thread.Name = "主线程";
        }
        public void tzm()
        {
            Console.WriteLine($"当前任务执行在{Thread.CurrentThread}");
            Thread.Sleep(5000);
            MessageBox.Show("调好了");

        }
        private void button1_Click(object sender, EventArgs e)
        {
            tzm();
        }

        private void button2_Click(object sender, EventArgs e)
        {

            MessageBox.Show("给");
        }

        private void button3_Click(object sender, EventArgs e)
        {
            ThreadStart threadStart = new ThreadStart(tzm);
            Thread thread = new Thread(threadStart);
            thread.Name = "分";
            thread.Start();
        }
    }
}

多线程案例

案例反映

  1. 有的错误(比如分线程操作UI报错)需要使用启动并调试才会检测到错误
  2. 从1中得出分线程不能操作UI(窗体,控件)

在这里插入图片描述

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _02_多线程案例
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Thread.CurrentThread.Name = "主";
        }
        void Jisun()
        {
            Console.WriteLine(Thread.CurrentThread.Name+"开始执行");
            int sum = 0;
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(1);
                sum+=i;
            }
            //需要使用启动并调试查看错误
            //因为lable1控件是由主线程创建的,分线程不能去操作
            
            //分线程不能操作UI(窗体,控件)
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Jisun();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            ThreadStart threadStart = new ThreadStart(Jisun);
            Thread thread = new Thread(threadStart);
            thread.Name = "分";
            thread.Start();
        }
    }
}

代码解释

线程通讯分传主

使用场景:有耗时任务交给分线程并执行,然后在需要的时候将分线程的结果传递回主线程。

关键点提取:分传主的两个方法

较为复杂:

  1. 定义一个变量存储主线程的执行期上下文
  2. 设置变量存储当前主线程的执行期上下文
  3. 启动分线程让他执行耗时任务
  4. 分线程调用方法,给主线程传递数据
  5. 定义函数,当分线程发送数据的时候执行

简单方法: Invoke在分线程中修改UI线程(主线程)中对象的属性(下小节详讲)
在这里插入图片描述

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _02_多线程案例
{
    public partial class Form1 : Form
    {
        //1.定义一个变量存储主线程的执行期上下文
        SynchronizationContext mainContent;
        public Form1()
        {
            InitializeComponent();
            Thread.CurrentThread.Name = "主线程";
        }
        void Fn()
        {
            Console.WriteLine(Thread.CurrentThread.Name);
            Thread.Sleep(3000);
            //4.分线程调用方法,给主线程传递数据
            mainContent.Post(Abc, "我是分线程计算的结果");
        }
        private void button1_Click(object sender, EventArgs e)
        {
            //线程通讯_主传分
            //2.设置变量存储当前主线程的执行期上下文
            mainContent = SynchronizationContext.Current;
            //ThreadStart threadStart = new ThreadStart(Fn);
            // Thread thread = new Thread(threadStart);
            //thread.Name = "分线程";
            //thread.Start();
            //启动分线程执行耗时任务
            //3.启动分线程让他执行耗时任务
            new Thread(new ThreadStart(Fn)) { Name = "分线程" }.Start();
        }
        //5.定义函数,当分线程发送数据的时候执行
        void Abc(object o)
        {
            //函数的参数就是第4步调用函数的时候传递的第二个参数
            Console.WriteLine(o);
            Console.WriteLine("Abc" + Thread.CurrentThread.Name);
            //主线程
            label1.Text = o.ToString();
        }
    }

}

代码解释

在这个示例中,有一个方法被封装为线程并执行,然后在需要的时候将计算结果传递回主线程。

  1. Fn():这个方法模拟了一个耗时操作。它让当前线程休眠3秒,然后使用mainContent.Post()方法将计算结果传递回主线程。

button1_Click事件处理器中,首先获取并保存了主线程的同步上下文,然后创建并启动了一个新的线程来执行Fn()方法。

线程通讯主传分

使用场景:你有一些可能会阻塞线程(如IO操作、网络请求、复杂计算等)的操作时,可以将它们封装为线程,并使用Thread类来异步执行它们。然后,在需要的时候,你可以使用Invoke()方法在UI线程中更新UI元素。这样可以避免阻塞UI线程或其他重要线程,提高应用程序的响应性和性能

本代码实例中,计算任务交给分线程,得出sum结果,需要显示在U世界界面的label1上

关键点提取

  • new Thread(Calc) { Name = "分线程" }.Start(100);:创建一个新的线程,设置其名称为"分线程",并立即启动它,传递参数100给Calc()方法。
  • Invoke(new Action(() => {...})):在UI线程中执行指定的操作。
  • 如果分线程执行的任务方法要接收参数,只能接收object类型,调用Start方法的时候 传递参数即可

在这里插入图片描述

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _03线程通讯主传分
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Calc(object o)
        {
            int sum = 0;
            for (int i = 0; i < Convert.ToInt32(o); i++)
            {
                Thread.Sleep(1);
                sum += i;
            }
            //Invoke在分线程中修改UI线程(主线程)中对象的属性
            //参数是一个委托类型
            Invoke(new Action(() =>
            {
                label1.Text = $"1到{o}的和为{sum}";
            }));
        }

        private void button1_Click(object sender, EventArgs e)
        {

            //主线程
            // Calc();
            // ThreadStart 没有参数的函数类型
            //1.创建接收参数的委托
            //ParameterizedThreadStart ts = new ParameterizedThreadStart(Calc);
            2.创建线程
            //Thread thread = new Thread(ts);
            //thread.Name = "分线程";
            3.执行线程
            //thread.Start(100);//调用Start方法的时候传递参数即可

            //简写形式
            //注意:如果分线程执行的任务方法要接收参数,只能接收object类型,调用Start方法的时候 传递参数即可
            new Thread(Calc) { Name = "分线程" }.Start(100);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            new Thread(Calc) { Name = "分线程" }.Start(300);
        }

        private void button3_Click(object sender, EventArgs e)
        {
            new Thread(Calc) { Name = "分线程" }.Start(200);
        }
    }
}

代码解释

在这个示例中,有一个方法被封装为线程并执行,然后在需要的时候被终止。

  1. Calc(object o):这个方法计算从1到指定数(由参数o决定)的和,并在计算完成后更新UI元素label1的文本。

button1_Click, button2_Click, 和 button3_Click事件处理器中,分别创建并启动了新的线程来执行Calc()方法,并传递了不同的参数。

关闭线程

使用场景:当一个线程使用完毕,需要使用手动关闭时

关键点提取

  • new Thread(Fn):创建一个新的线程,但不立即启动。
  • thread.Start():启动线程。
  • thread.Abort():终止线程。
    在这里插入图片描述
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _04关闭线程
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        void Fn()
        {
            Thread.Sleep(1000);
            Console.WriteLine("1秒过去了");
            Thread.Sleep(1000);
            Console.WriteLine("2秒过去了");
            Thread.Sleep(1000);
            Console.WriteLine("3秒过去了");
            Thread.Sleep(1000);
            Console.WriteLine("4秒过去了");
        }
        Thread thread;
        private void button1_Click(object sender, EventArgs e)
        {
            //Start() 开启线程
            thread = new Thread(Fn);
            thread.Start();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //销毁当前执行的线程

            thread.Abort();
        }
    }
}

代码解释

在这个示例中,有一个方法被封装为线程并执行,然后在需要的时候被终止。

  1. Fn():这个方法模拟了一个耗时操作。它让当前线程休眠4秒,并在每秒结束时打印一条消息。

button1_Click事件处理器中,创建并启动了一个新的线程来执行Fn()方法。

button2_Click事件处理器中,终止了上述创建的线程。

线程锁

使用场景:当有多个线程需要访问和修改同一个共享资源(如变量、数据结构、文件等)时,就需要使用线程锁来保证操作的安全性。否则,可能会出现数据竞争和不一致的问题。

关键点提取

  1. 创建一个锁(锁一般是一个引用类型 private static readonly object key = new object();
  2. lock (key) {...}:给需要保护数据的地方加锁
  3. 使用锁的注意事项
    1. lock锁括号中使用的锁必须是引用类型,string除外
    2.推荐锁使用静态的、私有的、只读的对象
    3.我们的锁一定要保证不会被对象的外部所操作才有意义,否则就有可能被手动上锁造成死锁
    在这里插入图片描述
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _05线程锁
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            new Thread(Fn1).Start();
            new Thread(Fn2).Start();
            new Thread(Fn3).Start();
          

        }
      
        void Fn1()
        {
            Thread.Sleep(100);
            Invoke(new Action<string>(changui), "1号线程修改的内容");
        } void Fn2()
        {
            Thread.Sleep(100);
            Invoke(new Action<string>(changui), "2号线程修改的内容");
        } void Fn3()
        {
            Thread.Sleep(100);
            Invoke(new Action<string>(changui), "3号线程修改的内容");
        }
        void changui(object o)
        {
            this.label1.Text = o.ToString();
        }
        //每次点击按钮lable都显示不同的文本,因为不同的线程执行的先后顺序不一定相同
        //执行完成的时刻也不一定相同,因此我们无法掌控某个线程执行的实际,
        // 上面是哪个线程谁都可以先执行完毕,也有可能同时执行完毕


        //1.创建一个锁(锁一般是一个引用类型)
        private static readonly object key = new object();
        
        //多个线程操作一个变量,导致我们的判断无法进行限制
        int apple;
        //使用锁的注意事项
        //1、lock锁括号中使用的锁必须是引用类型,string除外
        //2、推荐锁使用静态的、私有的、只读的对象
        //3、我们的锁一定要保证不会被对象的外部所操作才有意义,否则就有可能被手动上锁造成死锁
        private void button2_Click(object sender, EventArgs e)
        {
            apple = 1;
            new Thread(lisiEat).Start();
            new Thread(zhangsanEat).Start();
           
        }
        void zhangsanEat()
        {
            Console.WriteLine("张三吃苹果,去看看还有没有");
            //2.使用锁将代码锁起来,(互斥的,相互排斥的锁,当锁关闭的时候,另一个位置无法进入)
            lock (key)
            {
                if (apple <= 0)
                {
                    Console.WriteLine("张三:没有苹果了,不吃了");
                    return;
                }
                Thread.Sleep(100);
                apple--;
            }
            Console.WriteLine($"张三吃完了,现在还剩{apple}个苹果");
            Thread.Sleep(2000);
            Console.WriteLine("张三吃完了");
        }
        void lisiEat()
        {
            Console.WriteLine("李四吃苹果,去看看还有没有");
            lock (key)
            {
                if (apple <= 0)
                {
                    Console.WriteLine("李四:没有苹果了,不吃了");
                    return;
                }
                Thread.Sleep(100);
                apple--;
            }
            Console.WriteLine($"李四吃完了,现在还剩{apple}个苹果");
            Thread.Sleep(2000);
            Console.WriteLine("李四吃完了");
        }

      
    }
}

代码解释

button1_Click事件处理器中,创建了三个新的线程,分别执行Fn1(), Fn2(), 和 Fn3()方法。这三个方法都会尝试修改UI元素label1的文本,但由于它们是在不同的线程中运行的,所以我们无法预测它们的执行顺序和完成时间。

button2_Click事件处理器中,创建了两个新的线程,分别执行zhangsanEat()lisiEat()方法。这两个方法都会尝试修改共享变量apple的值,因此需要使用线程锁来保证操作的安全性。

在这里插入图片描述

总的来说,多线程是一种强大而复杂的技术,它可以让你的程序更加响应用户输入,更有效地利用多核处理器,并同时执行多个任务。然而,多线程编程也带来了一些挑战,如数据竞争死锁活锁等问题。因此,你需要深入理解多线程的原理技术,才能有效地使用它。

如果觉得文章还不错,可以点赞、收藏和转发,以支持作者继续创作更多教程。 另外本专栏将会持续更新,作者专栏中有已经更新完毕的C#基础教程!!!

标签:Thread,void,System,new,进阶篇,线程,易懂,using,多线程
From: https://blog.csdn.net/2401_82410658/article/details/137611851

相关文章

  • 用本小组项目中实际的例子来重现如下问题: 1、代码覆盖率对于“应该写但是没有写的代
    例子1-代码覆盖率无法检测资源管理问题:假设在移动充电桩应用中有一个负责与服务器通信的模块,它从服务器下载充电站的实时状态信息。开发者编写了一段代码来连接服务器、发送请求并接收响应数据,但是在处理完响应后,忘记关闭网络连接或释放相关资源:JavapublicclassChargingSta......
  • 通俗易懂关于Paxos的直观解释
    一、Paxos是什么在分布式系统中保证多副本数据强一致性算法。没有paxos的一堆机器,叫做分布式有paxos协同的一堆机器,叫分布式系统这个世界上只有一种一致性算法,那就是Paxos…-GoogleChubby的作者MikeBurrows其他一致性算法都可以看做Paxos在实现中的变体和扩展,比如ra......
  • SQL Server索引进阶篇----系列文章
    SQLServer索引进阶第一篇:索引介绍索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就获取到的,很多的技术人员因为不恰当的创建索引,最后使得其效果适得其反,可以说“成也索引,败也索引”。本......
  • 多线程 p2
    多线程概念​Java中的多线程概念指的是在java程序中同时执行多个进程的技术。Java提供了内置的多线程支持。Java的多线程编程可以用于实现并行计算、提升程序的响应性、处理异步任务等场景JAVA线程实现/创建方式1.继承Thread类​//继承Thread类,重写run()方法,调用start......
  • 《架构风清扬-Java面试系列第19讲》解释一下Java中的“volatile”在多线程环境中的作
    适用范围:这道题适应范围挺宽的,各个年限都可以用参考答案:主要用于确保变量在多个线程之间的可见性和有序性。可见性:当一个线程修改了被volatile修饰的变量,其他线程能够立即看到修改后的值。这确保了变量在多个线程之间的可见性。有序性:volatile关键字能够防止指令重排序......
  • 多线程
    多线程一、多线程理论[1]什么线程在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程线程顾名思义,就是一条流水线工作的过程一条流水线必须属于一个车间,一个车间的工作过程是一个进程车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线......
  • Java基础知识-面向对象编程(OOP)-Java集合框架-多线程和并发-Spring框架
    Java基础知识:Java的四种基本数据类型是:byte、short、int、long(整数类型)、float、double(浮点类型)、char(字符类型)、boolean(布尔类型)。它们之间的区别主要在于占用的内存大小和表示范围不同。Java中的String是不可变的意味着一旦String对象被创建,它的值就不能被修改。这意味着St......
  • 简述多线程中的锁与sleep
    面试中经常被问到,在多线程/加锁环境下使用sleep可能出现的问题,首先总结一下这些问题基本都出自sleep不会释放锁这一点(与wait()截然相反)。1sleep可能会引发的问题线程持有锁时休眠:当一个线程在持有锁的情况下调用sleep()时,它会在睡眠时仍保持锁的状态,此时其他线程将无法访......
  • Git教程(通俗易懂版本)
    什么是Git?Git其实就是一个帮助我们管理文件的工具,尤其适合程序员用来管理他们的代码文件。我们写代码的时候,经常会修改、添加或者删除一些文件,Git就能帮我们把这些文件的每一次变化都记录下来。比如说,你写了一个程序,然后修改了一些功能,Git就能帮你记住这次修改。如果你后来发......
  • 效率工具RunFlow完全手册之进阶篇
    欢迎来到RunFlow手册的进阶篇,如果您还不了解RunFlow,建议先阅读我们的基础篇。(Solo社区投稿)搜索文件按文件大小过滤,添加len参数,比如:len:1kb-2kb,len:3mb-5mb等等。**可以匹配多级目录,比如您想查找文件夹folder_a里面以test开头的文件,可以这样输入:folder_a/**/test。......