首页 > 编程语言 >c# 多线程编程

c# 多线程编程

时间:2023-05-07 18:44:26浏览次数:40  
标签:同步 Monitor c# 代码 编程 池中 线程 多线程

涉及的类

  • Thread //用于手动创建线程
  • ThreadPool //线程池
  • System.Threading.CancellationTokenSource //用于取消线程池线程
  • Monitor //线程同步

线程(Thread)与进程

当我们打开一个应用程序后,操作系统就会为该应用程序分配一个进程ID。进程可以理解为一块包含了某些资源的内存区域,操作系统通过进程这一方式把它的工作划分为不同的单元。一个应用程序可以对应多个进程(比如应用双开)。

线程与进程之间的关系可以理解为:线程是进程中的独立执行单元,操作系统通过调度线程来使应用程序工作;而进程则是线程的容器,它由操作系统创建,又在具体的执行过程中创建了线程。一个进程中至少包含一个线程,我们把该线程称为主线程

一个应用程序对应一个或多个进程,每个进程至少对应一个线程。

线程的调度

操作系统为每个线程分配了0~31中的某一级优先级,而且会把优先级高的线程优先分配给CPU去执行。程序可以通过设置Thread的Priority属性来改变线程的优先级,该属性的类型为ThreadPriority枚举类型,其成员包括Lowest、BelowNormal、Normal、AboveNormal和Highest。

在操作系统课程中,老师会介绍说“Windows是抢占式多线程操作系统”。之所以说它是抢占式的,是因为线程可以在任意时间里被抢占,来调度另一个线程。操作系统为每个线程分配了0~31中的某一级优先级,而且会把优先级高的线程优先分配给CPU去执行。

Windows支持7个相对线程优先级:Idle、Lowest、BelowNormal、Normal(默认)、AboveNormal、Highest和Time-Critical。

线程也分前后台

线程有前台线程和后台线程之分。在一个进程中,当所有前台线程停止运行后,CLR会强制结束所有仍在运行的后台进程,这些后台进程会直接被终止,而不会抛出异常。主线程将一直是前台线程

我们可以使用Thread类来创建线程(默认为前台线程),该类有三个构造函数,都会使用某委托类型的形参,该形参为线程绑定一个函数。

image-20230207095129267

注意:你在写多线程应用程序的时候,一定要区分前台线程和后台线程,否则很可能会产生疑惑:自己写的代码为什么没有被执行? 还要记得: 通过在主函数中让后台线程调用Join方法,可以保证主线程会在后台线程执行结束后才开始运行。

Thread.CurrentThread.ManagedThreadId 这个属性可用于获取当前的线程ID。

线程池

why、what、how

由来:通过Thread类来手动创建或销毁线程是比较耗费时间的。为避免这样的性能损失,就引入了线程池。

线程池是指用来存放应用程序中要使用的线程集合,这种集中存放的方式有利于对线程进行管理或重复利用。

CLR初始化时,线程池中是没有线程的。在内部,线程池维护了一个操作请求队列,当应用程序想要执行一个异步操作时,你需要调用QueueUserWorkItem方法来将对应的任务添加到线程池的请求队列中。线程池实现的代码会从队列中提取任务,并将其委派给线程池中的线程去执行。

如果线程池中没有空闲的线程,线程池就会创建一个新线程去执行提取的任务。而当线程池线程完成了某个任务后,线程也不会被销毁,而是返回到线程池中,等待响应另一个请求。由于线程不会被销毁,所以也就避免了由此产生的性能损失。由线程池创建的线程是后台线程,且它的默认优先级为Normal。

要使用线程池中的线程,需调用静态方法:ThreadPool.QueueUserWorkItem,以指定线程要运行的方法。这个静态方法有两个重载的版本。

public static bool QueueUserWorkItem ( WaitCallBack callback ) ;

public static bool QueueUserWorkItem ( WaitCallBack callback,Object state ) ;

这两个方法用于向线程池队列添加一个工作项(work item)以及一个可选的状态数据。然后这两个方法就会立即返回。工作项是指一个由callback参数标识的委托对象,被委托对象包装的回调方法将由线程池线程来执行。传入的回调方法必须匹配System.Threading.WaitCallBack委托类型,该委托定义如下:

public delegate void WaitCallBack ( Object state ) ;

线程池中的线程

线程池中的线程是由CLR来管理的。线程池使得线程可以充分有效地被使用,减少了任务启动的延迟。但不是所有的情况都适合使用线程池中的线程,注意:

  • 任务运行的时间比较短(<250ms),这样CLR可以充分调配现有的空闲线程来处理该任务;
  • 耗时长或有阻塞情况的不用线程池中的线程。

要使用线程中的线程,主要有下面两种方式:

// 方式1:Task.Run,.NET Framework 4.5 才有
Task.Run (() => Console.WriteLine ("Hello from the thread pool"));

// 方式2:ThreadPool.QueueUserWorkItem
ThreadPool.QueueUserWorkItem (t => Console.WriteLine ("Hello from the thread pool"));

创建不用线程池中的线程,可以直接通过new Thread()来创建,也可以通过下面的代码来创建:

Task task = Task.Factory.StartNew (() => ...,TaskCreationOptions.LongRunning);// 注意必须带TaskCreationOptions.LongRunning参数

关于线程的知识很多,这里不再深入了,因为这些已经足够让我们应付Web开发了。

协作式取消线程池线程

直接看代码就懂了

image-20230207101529819

线程同步

线程同步技术是指在多线程程序中,为了保证后者线程在只有等待前者线程完成之后才能继续进行。即在使用多线程技术的场景中,确保某一时刻只有一个线程来操作共享资源

举例来说,火车票售票系统程序允许多人同时购票,因此该线程肯定采用了多线程技术。但由于系统中有多个线程对同一资源(火车票)进行操作,我们必须确保只有在其他线程执行结束后,新的线程才开始执行。这样可以避免多位顾客买到同一张车票。此时需要使用的就是线程同步技术。

监视器对象(Monitor)能够确保线程拥有对共享资源的互斥访问权。C#通过lock 语句来提供简化的语法。

image-20230207103509115

image-20230207103530677

我们必须把Monitor.Exit函数放在finally语句块中,因为finally语句块中的代码即使在线程退出时也一样会执行。

所以在实际的开发过程中,要尽量避免使用线程同步技术,避免使用共享数据(如静态字段等)。全局共享数据,要是只读的倒还行,不用考虑线程同步问题,要是可编辑的,那最好不要用线程同步技术。

除了Monitor对象来实现线程同步,我们还可以使用Interlocked、信号量和Mutex等对象来实现线程同步,它们的使用方式与Monitor类似,大家可以自行参考MSDN,进行试验。

线程安全

这是针对共享数据被并发访问(且有进行更新的可能)时,保证数据访问总是按照先来后到的顺序来处理,避免造成数据修改上的混乱。

线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

线程安全问题大多是由全局变量静态变量引起的,若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,(被访问对象同一时刻只接收一个访问处理,别的等着)否则的话就可能影响线程安全。

并发、同步、异步、锁

并发:某一资源(数据或方法),同时被多个线程访问,这种情况就叫并发。

同步:按一定的顺序做某事,前一件事做完之后,后续的事才能开始。

异步:同时进行多个事项的处理。

锁这个用处,可以想象动车里的卫生间,有人进去后门就立马锁上了,别人进不去,想进去得等待。等上一个人进去办完事儿出来了,卫生间门的锁开了,下一个人才可以进去。

c#常见锁(Lock,也叫排它锁或互斥锁)

互斥锁 lock 语句

作用:将会锁住代码块的内容,并阻止其他线程进入该代码块,直到该代码块运行完成,释放该锁。

注意:

  • 定义的锁对象应该是 私有的、静态的、只读的、引用类型的对象,这样可以防止外部改变锁对象 。
  • 避免对不同的共享资源使用相同的 lock 对象实例,因为这可能导致死锁或锁争用。

参考:

lock 语句 - 同步对共享资源的线程访问 | Microsoft Learn

image-20230501182939231

互斥锁 Monitor

作用:将会锁住代码块的内容,并阻止其他线程进入该代码块,直到该代码块运行完成,释放该锁。

注意:定义的锁对象应该是 私有的、静态的、只读的、引用类型的对象,这样可以防止外部改变锁对象 。

使用示例:

private static readonly object Lock = new object();

try{
    Monitor.Entry(Lock);
    //todo ……
}
finally{
    Monitor.Exit(Lock);
}

以上代码,等同于lock语句。

读写锁

读写锁允许在有其他程序正在写的情况下读取资源,所以如果资源允许脏读,用这个比较合适。

private static ReaderWriterLockSlim LockSlim = new ReaderWriterLockSlim();

private static int lockSlimInt;

private static void LockSlimIntAdd()

{

    for (var i = 0; i < runTimes; i++)

    {

        LockSlim.EnterWriteLock();

        lockSlimInt++;

        LockSlim.ExitWriteLock();

    }

}

更新于:2023-05-07

标签:同步,Monitor,c#,代码,编程,池中,线程,多线程
From: https://www.cnblogs.com/idasheng/p/17379772.html

相关文章

  • C#中应用程序集的装载过程详解
    原文:https://blog.csdn.net/chinaherolts2008/article/details/114325104这篇文章主要介绍了C#中应用程序集的装载过程的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧了解程序集如何在C#.NET中加......
  • C# 编程常见错误集锦
    常见错误集锦Microsoft.Data.SqlClient.SqlException证书链是由不受信任的颁发机构颁发的。该解决方式需要配置数据库连接字符串:直接在“数据库连接字符串最后面”增加证书信任的配置。;TrustServerCertificate=true2.已提交到GitLab中的项目如何添加.gitignore文件......
  • FreeCodeCamp-通过编写小测验学习无障碍
    index.html<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width,initial-scale=1.0"/><metaname="de......
  • thusc游寄
    Day0坐高铁到南京,前面就是CJ老哥,感觉CJ老哥好卷,还卷whk,搞得我这个摆怪都想卷会。结果卷了半个小时,发现越卷越难受,开始还看得懂点东西,后面啥都看不懂,算了开摆,车上不好打原,直接打舟。上车前出了个缪,下车后出了个缪,把yehaodxv、智子和康老师都看呆了。方舟真好玩,但我是原批。晚上在......
  • DC-1 find提权/sql数据库创建用户(个人笔记)
    进入数据库select*fromusers\G;\G为了让界面看着更整洁 在exploitdb中有一个针对Drupal7版本的攻击脚本,可以增加一个admin权限的用户账号:终端/msf输入:searchsploitdrupalpython2/usr/share/exploitdb/exploits/php/webapps/34992.py-thttp://url-uadmin3-pad......
  • 【C#】数组深拷贝
    数组是引用类型,元素保存在堆上,栈上保存的是地址。1.Buffer.BlockCopyint[]arrold=newint[100000];int[]arrnew=newint[100000];//foreach不能修改遍历集合的元素内容for(inti=0;i<arrold.Length;++i){arrold[i]=i;}Stopwatchsw=newStopwatch(......
  • LeetCode 349. 两个数组的交集
    题目链接:LeetCode349.两个数组的交集题意:本题题意是让我们找出两个数组中的交集,注意交集中不能出现重复元素解题思路:思路比较常规,先遍历数组num1,对于每个首次出现的数字,对应位置上的数值+1,再遍历数组num2,判断当前数字是否在num1中出现,如果出现,就加入到结果集中完整代码如......
  • LeetCode 242. 有效的字母异位词
    题目链接:LeetCode242.有效的字母异位词题意:本题是要判断两个字符串s和t,是否是字母异位词,所谓字母异位次就是如果s和t中每个字符出现的次数都相同,则称s和t互为字母异位词。解题思路:首先我们很容易想到,最简单的思路就是先遍历一遍s字符串,统计出每个字母出现的次数......
  • 交换机重置console口密码
    1、通过Console口连接华为交换机,手动重启交换机。2、界面出现BIOS LADING...,按下快捷键“Ctrl+B”并输入BootROM/BootLoad密码,进入BootROM/BootLoad主菜单。3、初始密码:[email protected]  A必须大写。4、选择7 Clearpasswordforconsoleuser 清除console用户密码模式......
  • 转行去做人工智能之 初试C语言
    C语言学习大纲:C语言基础变量、数据类型、运算符控制流语句(if、for、while)函数数组、指针C语言进阶结构体动态内存分配文件操作预处理器C语言高级特性多线程编程指针高级应用系统编程推荐学习资源:https://github.com/moocstudent/c_mooc_learning_from_w......