首页 > 编程语言 >并发编程 - 初识线程

并发编程 - 初识线程

时间:2025-01-15 22:21:19浏览次数:1  
标签:优先级 Thread 编程 初识 线程 thread1 停止 执行

01、什么是线程?

要深刻理解什么是线程,就需要了解计算机的发展史,需要了解多任务概念,需要了解进程概念,然后才是线程概念。因为我们主要还是讲解线程,因此这里就不进行展开说其他概念了,有兴趣的可以自行了解下。

简单来说,线程就是操作系统中能够单独执行任务的最小单元。

对于大多数编程语言来说,都有一个或者类似的功能的Main()方法,而该方法中的所有代码也都是按照顺序一行一行的执行,如果要想执行下一行代码那么就必须等待上一行代码执行完成。而线程作为能够单独执行任务的最小单元,因此线程可以使得应用程序的一部分独立于另外一部分而单独运行,这也就意味着我们可以改变程序的常规代码执行顺序,从而达到更复杂的程序控制。

对于一个应用程序来说,要想正常运行至少要有一个线程,通常为主线程,也就是上文中提到的Main()方法,当调用此方法时系统就会自动创建一个主线程。

02、后台线程和前台线程

线程可以分为前台线程和后台线程,两者基本完全相同,唯一区别是前台线程可以在托管执行环境中一直运行,而后台线程不可以。简单来说就是当进程中所有前台进程都停止后,系统会自动停止并关闭该进程内的所有后台线程。

在C#中可以通过Thread.IsBackground查看当前线程类型,也可以通过该属性修改线程类型。默认情况下,通过Thread对象新建并启动的所有线程都是前台线程。

看如下简单示例:

public static void CreateThread()
{
    Console.WriteLine($"主线程 是否为后台线程:{Thread.CurrentThread.IsBackground}");
    var thread1 = new Thread(()=> Console.WriteLine("Hello World"));
    Console.WriteLine($" 线程1 默认为后台线程:{thread1.IsBackground}");
    thread1.IsBackground = true;
    Console.WriteLine($" 线程1 设置为后台线程:{thread1.IsBackground}");
    thread1.Start();
}

执行效果如下:

03、线程的优先级

线程作为操作系统中能够单独执行任务的最小单元,那么当一个进程中有多个线程时,应该先执行那个线程呢?因此线程需要一个标记其执行优先级的属性。

在C#中Thread可以通过Priority来设置线程的优先级,告诉系统应该先执行谁。ThreadPriority有以下5种类型:

  • Lowest: 最低优先级,在所有优先级中最低,在所有线程中可位于最后执行。
  • BelowNormal: 低于正常优先级,在Normal优先级之后,在Lowest优先级之前。
  • Normal: 默认优先级,线程默认的优先级
  • AboveNormal: 高于正常优先级,在Highest优先级的线程之后,在Normal优先级之前。
  • Highest: 最高优先级,在所有优先级中最高,在所有线程中可优先执行。

下面我们做个简单的测试,用来验证优先级不同的导致差异。

class ThreadPriorityTest
{
    //是否执行,确保一个线程修改此值后,其他线程立刻查看到最新值
    static volatile bool isRun = true;
    //确保每个线程都有独立的副本存储计数统计值
    [ThreadStatic]
    static long threadCount;
    //停止运行
    public void Stop()
    {
        isRun = false;
    }
    //打印线程名称对应优先级以及计数总数
    public void Print()
    {
        threadCount = 0;
        while (isRun)
        {
            threadCount++;
        }
        Console.WriteLine($"{Thread.CurrentThread.Name} 优先级为{Thread.CurrentThread.Priority,8} 总执行计数为:{threadCount,-13:N0}");
    }
}
public static void PriorityTest()
{
    var threadPriorityTest = new ThreadPriorityTest();
    //创建3个线程,并设置优先级
    var thread1 = new Thread(threadPriorityTest.Print)
    {
        Name = "线程1"
    };
    var thread2 = new Thread(threadPriorityTest.Print)
    {
        Name = "线程2",
        Priority = ThreadPriority.Lowest
    };
    var thread3 = new Thread(threadPriorityTest.Print)
    {
        Name = "线程3",
        Priority = ThreadPriority.Highest
    };
    //启动3个线程
    thread1.Start();
    thread2.Start();
    thread3.Start();
    //休眠3秒
    Thread.Sleep(10000);
    //停止运行
    threadPriorityTest.Stop();
    //等待所有线程完成
    thread1.Join();
    thread2.Join();
    thread3.Join();
}

执行效果如下:

可以发现优先级越高,其执行计数值越大。

其中需要注意的是volatile和ThreadStatic的用法。

在这个多线程示例中我们需要准确的统计不同的线程执行计数,因此正常来说可能需要设置多个变量用来对应存储各自线程的统计计数,很显然这样会导致代码臃肿。因此我们选用了另一种办法,使用ThreadStatic标记一个字段,使得该字段对每个线程都有独立的副本。这样可以做到线程之间不会共享这个字段的值,同时还可以做到多个线程只用这一个字段。

另外对于多线程共享的变量,很可能因为CPU缓存导致多个线程共享的变量不一致问题,因此通过volatile告诉编译器和运行时每次访问该字段时都要直接从内存中读取最新值,以此来保证线程之间的可见性。

04、线程的生命周期

当一个线程被创建后,会经历多个状态,包括未启动、已启动、执行、睡眠、挂起等十个状态,同时Thread类也提供了一些方法,用来控制当前线程状态,比如启动、停止、恢复、中止、挂起以及等待线程等方法。

我们先来看看线程具体有哪些状态:

  • Running(运行)—— 线程已启动,而且没有停止;
  • StopRequested(请求停止) —— 请求停止线程;
  • SuspendRequested(请求挂起) —— 请求线程挂起;
  • Background(后台) —— 线程在后台执行;
  • Unstarted(未启动) —— 还没有在线程上调用 Start()方法;
  • Stopped(停止) —— 线程已完成了其所有的指令,而且已经停止;
  • WaitSleepJoin(等待睡眠连接) —— 通过调用 Wait()、Sleep()或 Join()方法,来暂停线程;
  • Suspended(挂起) —— 线程处于挂起状态;
  • AbortRequested(请求中止) —— Abort()方法已调用,但是线程还没有收到试图终止自己的 System.Threading.ThreadAbortexception,也就是说,线程还没有停止但不久就会停止;
  • Aborted****(中止) —— 线程处于停止状态,但不一定已执行完毕;

下面我们再来看看线程的常用方法。

  • Start(): 启动线程,使其状态变更为Running。
  • Sleep(): 把正在运行的线程暂停一段时间后自动恢复,线程状态保持活跃。
  • Suspend():[已弃用]暂停当前线程的执行,直到调用 Thread.Resume 显式恢复。
  • Resume():[已弃用]恢复一个已被暂停的线程。
  • Interrupt(): 中断处于 WaitSleepJoin 线程状态的线程。
  • Join(): 阻塞调用线程,直到某个线程终止时为止。
  • Abort():[已弃用]终止当前线程。

通过源码可以看到Resume和Suspend方法被弃用的原因。这是因为它们有很多问题和缺陷。使用它可能会导致程序的不稳定、死锁或者资源竞争问题。因此,它已经被标记为废弃,不推荐再使用。

在多线程编程中,通常可以通过合理的同步机制来控制线程的执行。比如,使用上述的 Monitor、Mutex、Event 和 Semaphore 来协调多个线程的行为,确保资源访问的安全和正确性。

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner

标签:优先级,Thread,编程,初识,线程,thread1,停止,执行
From: https://www.cnblogs.com/hugogoos/p/18673823

相关文章

  • 27. 多线程技术
    一、多线程技术  QThread类是PySide中的核心线程类,要实现一个线程,需要创建QThread类的有一个子类,并且实现其run()方法。  线程也有自己自己的生命周期,其中包含5种状态,分别为:新建状态、就绪状态、运行状态、阻塞状态**和死亡状态。新建状态就是线程被创建时的状......
  • 从线程到协程:理解现代编程中的并发革命(一)
    在现代软件开发中,性能与效率始终是工程师追求的目标,而并发编程正是实现这一目标的关键手段。从传统的线程模型到轻量级的协程技术,编程范式正经历一场深刻的变革。线程为我们带来了并发的能力,但伴随而来的是高昂的资源成本和复杂的管理难度。而协程的出现,则为开发者提供了一种更......
  • 【快速入门|文末福利】运筹学|初识线性规划(一条逻辑线,只需初中数学基础)
    导学问题/回忆自测三个核心问题“线性”为何?何谓“标准”?如何“化归”(把一般的线性规划问题转化为标准的线性规划问题)提示字面意思,在三个要素、两个关系之间对三个要素的要求“大”、“大”、“等”反转(乘以-1)/补齐/“分身”逻辑线索(逻辑线索中,发现有不熟悉的名词没关系,......
  • flask之 scoped实现线程安全.py
    1、用法导入模块,将Session传入scoped_session即可fromsqlalchemy.ormimportsessionmakerfromsqlalchemyimportcreate_enginefromsqlalchemy.ormimportscoped_sessionfrommodelsimportUsersfromthreadingimportlocalengine=create_engine("mysql+pymysql:......
  • 美团动态线程池
    使用线程池ThreadPoolExecutor过程中你是否有以下痛点呢?代码中创建了一个ThreadPoolExecutor,但是不知道那几个核心参数设置多少比较合适凭经验设置参数值,上线后发现需要调整,改代码重启服务,非常麻烦线程池相对开发人员来说是个黑盒,运行情况不能及时感知到,直到出现问题......
  • 线程每次iodelay监控及D状态开始和结束监控并做堆栈记录
    一、背景在之前的博客 获取进程或线程级别的iodelay的方法_io验证延时链-CSDN博客里,我们讲到了获取进程或线程的iodelay的方法,但是博客里讲到的获取iodelay的值是一个累积值,并不能准确的捕获到每个单次的iodelay具体是多少。这篇博客里是为了监控每个单次的iodelay,除了监控i......
  • Java多进程多线程处理详解
    在Java编程中,多进程和多线程是两种常见的并发编程技术,用于提高程序的执行效率和响应速度。本文将详细介绍Java中的多进程和多线程处理,包括理论概述和代码示例。通过本文,你将了解如何在Java中实现多进程和多线程,以及它们在实际应用中的价值和意义。一、理论概述1.多进程与多线程......
  • 可编程交流负载标准
    可编程交流负载标准是电力电子测试领域的重要组成部分,它为交流电源、变频器、逆变器等设备的测试提供了标准化的负载条件。这种可编程性使得测试更加灵活和精确,能够满足不同设备和应用场景的需求。核心在于其可编程性,这意味着用户可以根据自己的需求,通过编程来设定负载的各种参数......
  • 【IO编程】静态库 和 动态库
    在软件开发中,库是一组已编译的代码集合,提供了程序可以直接调用的功能模块(如数学运算、字符串处理、文件操作等)。库的主要作用是提高代码复用性、减少重复开发,并提供标准化功能。什么是库库(Library)是一个包含函数、类或其他可重用代码的集合。开发者在程序中调用库中的函......
  • 【程序猿面试真题——计算机基础知识和编程】如何寻找二次曲线(离散的点连成的)的最小值
    【程序猿面试真题——计算机基础知识和编程】如何寻找二次曲线(离散的点连成的)的最小值?【程序猿面试真题——计算机基础知识和编程】如何寻找二次曲线(离散的点连成的)的最小值?文章目录【程序猿面试真题——计算机基础知识和编程】如何寻找二次曲线(离散的点连成的)的最小......