首页 > 编程语言 >并发编程 - 线程浅试

并发编程 - 线程浅试

时间:2025-01-17 18:11:52浏览次数:1  
标签:Console CurrentThread Thread 编程 线程 WriteLine 浅试 Id

前面已经对线程有了初步认识,下面我们来尝试使用线程。

01、线程创建

在C#中创建线程主要是通过Thread构造函数实现,下面讲解3种常见的创建方式。

1、通过ThreadStart创建

Thread有一个带有ThreadStart类型参数的构造函数,其中参数ThreadStart是一个无参无返回值委托,因此我们可以创建一个无参无返回值方法传入Thread构造函数中,代码如下:

public class ThreadSample
{
    public static void CreateThread()
    {
        Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
        var thread = new Thread(BusinessProcess);
        thread.Start();
    }
    //线程1
    public static void BusinessProcess()
    {
        Console.WriteLine($"BusinessProcess 线程Id:{Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine("开始处理业务……");
        //业务实现
        Console.WriteLine("结束处理业务……");
    }
}

代码也相当简单,我们在主线程中通过Thread创建了一个新的线程用来运行BusinessProcess方法,同时通过Thread.CurrentThread.ManagedThreadId打印出当前线程Id。

代码执行结果如下,主线程Id和业务线程Id并不相同。

2、通过ParameterizedThreadStart带参创建

Thread还有一个带有ParameterizedThreadStart类型参数的构造函数,其中参数ParameterizedThreadStart是一个有参无返回值委托,其中参数为object类型,因此我们可以创建一个有参无返回值方法传入Thread构造函数中,然后通过Thread.Start方法把参数传递给线程,代码如下:

public static void CreateThreadParameterized()
{
    Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
    var thread = new Thread(BusinessProcessParameterized);
    //传入参数
    thread.Start("Hello World!");
}
//带参业务线程
public static void BusinessProcessParameterized(object? param)
{
    Console.WriteLine($"BusinessProcess 线程Id:{Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine($"参数 param 为:{param}");
    Console.WriteLine("开始处理业务……");
    //业务实现
    Console.WriteLine("结束处理业务……");
}

我们看看代码执行结果:

该方式有个限制,因为ParameterizedThreadStart委托参数为object类型,因此我们的业务方法也必须要用object类型接收参数,然后再根据实际类型进行转换。

3、通过Lambda表达式创建

通过上面可以知道无论ThreadStart还是ParameterizedThreadStart本质上都是一个委托,因此我们可以直接使用Lambda表达式直接构建一个委托。可以看看以下代码:

public static void CreateThreadLambda()
{
    Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
    var thread = new Thread(() =>
    {
        Console.WriteLine($"业务线程Id:{Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine("开始处理业务……");
        //业务实现
        Console.WriteLine("结束处理业务……");
    });
    //传入参数
    thread.Start();
}

代码执行结果如下:

因为Lambda表达式可以直接访问外部作用域中的变量,因此线程传参还可以使用Lambda表达式来实现。

但是这也导致了一些问题,比如下面代码执行结果应该是什么?先自己想想看。

public static void CreateThreadLambdaParameterized()
{
    Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
    var param = "Hello";
    var thread1 = new Thread(() => BusinessProcessParameterized(param));
    thread1.Start();
    param = "World";
    var thread2 = new Thread(() => BusinessProcessParameterized(param));
    thread2.Start();
}
//带参业务线程
public static void BusinessProcessParameterized(string param)
{
    Console.WriteLine($"业务线程Id:{Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine($"参数 param 为:{param}");
}

看看执行结果:

和你想想的结果一样吗?

这是因为当在Lambda 表达式中使用任何外部局部变量时,编译器会自动生成一个类,并将该变量作为该类的一个属性。因此这些外部变量并不是存储在栈中,而是通过引用存储在堆中,因此此时param参数实际上在内存中是一个类是一个引用类型,所以两个线程中使用的param都指向了堆中的同一个值。

并且使用Lambda表达式引用另一个C#对象的方式有个专有名词叫闭包。感兴趣的可以去了解下闭包概念。

02、线程休眠

可以通过Sleep方法暂停当前线程,使其处于休眠状态,以尽可能少的占用CPU时间。看如下示例代码,通过在Sleep方法前后打印出当前时间对比,来观察暂停线程效果。

public static void ThreadSleep()
{
    Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
    var thread = new Thread(() =>
    {
        Console.WriteLine($"业务线程Id:{Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine($"暂停线程前:{DateTime.Now:HH:mm:ss}");
        //暂停线程10秒
        Thread.Sleep(10000);
        Console.WriteLine($"暂停线程后:{DateTime.Now:HH:mm:ss}");
    });
    thread.Start();
    thread.Join();
}

代码执行结果如下:

可以发现暂停线程前后正好差了10秒钟。

03、线程等待

线程等待指让程序等待另一个需要长时间计算的线程运行完成后,再继续后面操作。而使用Thread.Sleep方法并不能满足需求,因为当前并不知道执行计算到底需要多少时间,因此可以使用Thread.Join。如上一小节中代码,当代码执行到Thread.Join方法时,则线程会处于阻塞状态,只有线程执行完成后才会继续往下执行。具体示例可以看上一小节。

04、线程其他方法

此外线程还有暂停、恢复、中断、终止等线程方法,这里就不介绍了,因为一些方法已经弃用没有必要再花经历学习了。

05、异常处理

对于线程中的异常需要特别注意,对于一个Thread子线程所产生的异常,默认情况下主线程并不能捕捉到,可以查看下面示例:

public static void ThreadException()
{
    Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
    try
    {
        var thread = new Thread(ThreadThrowException);
        thread.Start();
    }
    catch (Exception ex)
    {
        Console.WriteLine("子线程异常信息:" + ex.Message);
    }
}
//业务线程不处理异常,直接抛出
public static void ThreadThrowException()
{
    Console.WriteLine($"业务线程Id:{Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine("开始处理业务……");
    //业务实现
    Console.WriteLine("结束处理业务……");
    throw new Exception("异常");
}

运行结果如下:

可以看到在主线程中并没有捕捉到子线程抛出的异常,而导致程序直接中断。因此我们在处理线程异常时需要特别注意,可以直接在线程中处理异常。

06、何时应该使用线程

线程有很多优点,但也并不是万能的,因为每一个线程都会产生大量的资源消耗,包括:占用大量内存空间,线程的创建、销毁和管理,线程之间的上下文切换,以及垃圾回收的消耗。

举个简单例子,比如一个小餐馆,有一个厨师,一个下单员,客户下单给下单员,下单员把客户下的菜单传递给厨师。假如现在客户很多一个下单员忙不过来,老板决定再添加一个下单员,此时下单的效率可以提升一倍,但是厨师还是一个,那么就会导致当厨师和A下单员交接的时候,B下单员只能等着,并且因为之前厨师和A下单员长时间合作形成了彼此默契,这是再和B下单员交接的时候效率可能并不高,因此最终整体效率并不一定提升多少。如果把厨师比作CPU处理器,下单员比作线程,如果要想餐馆的整体效率提升那么在增加下单员的时候,必须要相应的添加厨师,才能使得餐馆最大效率的提升。

因此并不是说无脑的添加线程就可以使得程序效率提升,需要按需使用。

比如在以下使用场景可以考虑使用多线程:文件多写、网络请求、数据库查询、图像处理、数据分析、定时任务等。

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

标签:Console,CurrentThread,Thread,编程,线程,WriteLine,浅试,Id
From: https://www.cnblogs.com/hugogoos/p/18677486

相关文章

  • Java初学者笔记-03、代码块内部类函数式编程
    代码块静态代码块static{}类加载时自动执行,类只会加载一次,静态代码块只会执行一次,往往用来对类的静态资源的初始化。实例代码块{},每次创建对象时执行,用来完成对象的初始化的。内部类一个类定义在另一个类内部,叫做内部类。使用场景:当一个类的内部,包含了一个完整的事物,且......
  • 编程题-最小高度树
    题目:给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一棵高度最小的二叉搜索树。解法一(二分查找+二叉搜索树构建):二叉搜索树的中序遍历是升序序列,题目给定的数组是按照升序排列的有序数组,因此可以确保数组是二叉搜索树的中序遍历序列。二叉搜索树中,左子树的......
  • java 函数式编程
    1函数式创建对象new接口或抽象类时在花括号里面补全缺失的函数体可以创建匿名子类对象(非子类匿名对象)new普通类时在花括号里面直接重写方法可以创建匿名子类对象(非子类匿名对象)2lumbda表达式创建对象在函数式创建对象的基础上当接口或抽象类中仅有一个方法缺少函数体时可以......
  • 编程语言也给你挑上了
    现在的实习生都这么趾高气扬啦,会个java给你骄傲上了?上月组里调来了个实习生,说是上个导师训不服他。据说还是老板看上的可塑之才,有过独立游戏的开发经验。我倒是看看是个什么天才。听前科室说,这小子规范一塌糊涂。代码一堆嵌套,能省的全省了,隔着大肠包小肠呢,说自己一直都这么写......
  • linux内核态线程详解
    头文件:#include <linux/sched.h>     //wake_up_process()    #include <linux/kthread.h>   //kthread_create()、kthread_run()  #include <err.h>           //IS_ERR()、PTR_ERR()1.创建并启动一个内核线程:方式一:s......
  • 如何入门编程
    编程入门之路:从新手到开发者编程就像学习一门新语言,最开始总是有些让人畏惧。但当你开始理解那些字母组合的真正含义时,便会领悟到其美妙之处。那么,你准备好踏上这条旅程了吗?今天,我们将一起探讨如何顺利入门编程,打下坚实的基础,最终成为一名出色的开发者。选择合适的编程语......
  • libcurl多线程下载,支持断点续传
    libcurl多线程下载一步步实现创建时间:2024年12月1日17:35标签:libcurl,linux,下载,多线程最后编辑:2025年1月16日23:43平台是WSL的Ubuntu22,使用Gcc编译。单线程下载编译命令gcc-otranstrans.c-lcurl/*trans.c*/#include<curl/curl.h>#include<stdio.h>......
  • Java编程思想第四版第十五章第8题(java中的一些基本概念)
    1.编程题目概况这是Java编程思想(第四版)第十五章中的编程作业题第8题,原文如下:练习8.模仿Coffee示例的样子,根据你喜爱的电影人物,创建一个StoryCharacters的类层次结构,将它们划分为GoodGuys和BadGuys.再按照CoffeeGenerator的形式,编写一个StoryCharacters的生成......
  • 在线客服系统 QPS 突破 240/秒,连接数突破 4000,日请求数接近1000万次,.NET 多线程技术的
    背景我在业余时间开发了一款自己的独立产品:升讯威在线客服与营销系统。陆陆续续开发了几年,从一开始的偶有用户尝试,到如今的QPS突破240次/秒,连接数突破4000,日请求数接近1000万。(PS:阿里云真贵啊)在这篇文章中,我将简要介绍我在技术上做了哪些工作,我是如何做到的。PS:虽......
  • Haskell语言的编程范式
    Haskell语言的编程范式及其魅力引言Haskell是一种纯粹的函数式编程语言,自1987年首次发布以来,它一直在学术界和工业界保持着相对高的关注度。Haskell的编程范式与传统的命令式编程有着显著的不同,提供了一种更加优雅和强大的方式来处理计算和数据。本文将详细探讨Haskell语......