c#多线程
第五章 多线程
1.线程的创建、启动、挂起、恢复、休眠、终止
创建:
Thread mythread = New Thread(New ThreadStart(createThread)); //用线程起始点的ThreadStart委托创建该线程的实例
public static void createThread()
{
console.writeLine("创建线程");
}
//如果需要带参数创建线程时,可创建一个类,将参数和方法放入类中,实例化类,然后调用类的方法即可
//用该委托ParameterizedThreadStart创建的线程可以向线程传参
启动:mythread.Start(); //如果线程已经终止,就无法通过再次调用Start()方法来重新启动
mythread.Start(参数); //参数:一个对象 ,包含线程执行的方法要使用的数据
挂起:mythread.Suspend();
恢复:mythread.Resume();
休眠:mythread.Sleep(1000); //休眠一秒钟
mythread.Sleep(TimeSpan.FromSeconds(2)); //休眠两秒
注意:指定0为指示应挂起此线程让其他等待的线程执行;指定Infinite为无限期阻止线程。
等待: mythread.Join(); //阻塞调用线程直到某个线程终止时为止。
mythread.Join(参数); //参数:时间,单位ms 等待多长时间
终止: mythread.Abort(); //慎用 可能会造成程序崩溃
mythread.Abort(参数); //参数:一个对象,包含应用程序特定的信息,该信息可供被终止的线程使用
mythread.ResetAbort(); //取消当前线程请求的Abort
2.线程的优先级
Highest > AboveNormal > Normal > BelowNormal > Lowest //可通过线程的Priority属性设置和获取其优先级
//其中Normal是默认值
3.前台线程和后台线程
前台线程:只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程都是前台线程
后台线程:只要所有的前台线程结束,后台线程自动结束。通过Thread.IsBackground()设置后台线程,mythread.IsBackground = true。必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型。
4.线程的同步
指并发线程高效、有序的访问共享资源所采用的技术。
同步 即指某一时刻只有一个线程可以访问资源,只有当他用完或者放弃代码或资源的所有权时,其他线程才能使用这些资源。
1)lock关键字
Object thisLock = new Object();
lock(thisLock)
{
//要运行的代码块
}
注意:
[1]提供给lock的语句的参数必须是基于引用类型的对象,该对象用来定义锁的范围。严格来说,提供给lock语句的参数只是用来唯一标识由多个线程共享的资源,所以它可以是任意类的实例。
[2]最好避免锁定public类型或者不受应用程序控制的对象实例,否则可能导致死锁。最好锁定不会被暂留的私有或受保护的成员。
例: void LockThread()
{
lock(this)
{
代码块;
}
}
2)Monitor类
主要功能:
[1]它根据需要与某个对象相关联
[2]它是未绑定的,可以直接从任何上下文调用它
[3]不能创建 Monitor类的实例
常用方法:
[1]Monitor.Enter(); //在指定对象上获取排它锁
[2]Monitor.Exit(); //释放指定对象上的排它锁
[3]Monitor.Pulse(); //通知等待队列中的线程锁定对象状态的改变
[4]Monitor.PulseAll(); //通知所有等待线程锁定对象状态的改变
[5]Monitor.TryEnter(); //试图获取指定对象上的排它锁
[6]Monitor.Wait(); //释放对象上的锁并阻止当前线程,直到它重新获取该锁
例: void LockThread()
{
Monitor.Enter();
代码块;
Monitor.Exit();
}
3)Mutex类(可跨进程的线程同步)
常用方法:
[1]Close(); //在派生类中被重写时,释放由当前WaitHandle持有的所有资源
[2]OpenExisting(); //打开现有的已命名的互斥体
[3]ReleaseMutex(); //释放Mutex一次
[4]SignalAndWait(); //原子操作的形式,向一个WaitHandle发出信号并等待另一个
[5]WaitAll(); //等待指定数组中的所有元素都收到信号
[6]WaitAny(); //等待指定数组中的任一元素收到信号
[7]WaitOne(); //当在派生类重写时,阻止当前线程,直到当前的WaitHandle收到信号
例: void LockThread()
{
Mutex MyMutex = new Mutex(); //实例化Mutex类对象
MyMutex.WaitOne();
代码块;
MyMutex.ReleaseMutex();
}
注意:WaitOne()和ReleaseMutex()一一对应,即可重复调用请求,但需调用同样多次数的ReleaseMutex()来释放互斥体所有权
4)原子操作
使用Interlocked类提供的Increment、Decrement和Add等基本数学操作的原子方法,所以编写时无需使用锁
5)SemaphoreSlim类
static SemaphoreSlim sem = new SemaphoreSlim(4); //实例化 参数4为限制并发数为4个线程
sem.Wait();
/*代码块*/
sem.Release();
6)AutoResetEvent类
private static AutoResetEvent are1 = new AutoResetEvent(false);
private static AutoResetEvent are2 = new AutoResetEvent(false);
are1.Set();
are2.WaitOne();
/*****/
are1.Set();
are1.WaitOne();
/*****/
are2.Set();
are1.WaitOne();
/*****/
//定义了两个实例,可互相向对方发送信号。向AutoResetEvent构造方法传入false,则定义实例的初始状态为unsignaled,此时调用
WaitOne()方法将会被阻塞,直到调用Set()方法;如果传入true,则实例的状态为signaled,如果线程调用了WaitOne()方法则会被立即
处理。在处理之后,事件的状态会自动变为unsignaled,需要再对实例调用一次Set()方法,才能使调用WaitOne()方法的实例继续执行。
7)ManualResetEventSlim类
static ManualResetEventSlim mres = new ManualResetEventSlim(false);
mres.Set();
mres.Wait();
/*代码块*/
mres.Reset();
//与AutoReset类似,不同之处在于ManualReset需要手动调用Reset()方法进行切换事件的状态。
8)CountDownEvent类
static CountDownEvent cd = new CountDownEvent(2); //实例化 参数2指定两个操作完成发出信号,即Wait()接收到两个信号才可继续执行
/*代码块*/
cd.Signal(); //发送信号
cd.Wait(); //等待接收到指定数目的信号后才继续往下执行
/*代码块*/
cd.Dispose(); //释放所有占用的资源
//针对需要等待多个异步操作完成的情形,该方式非常便利。但有一个重大的缺点就是:如果调用的Signal()方法没有达到指定次数
那么 Wait()将一直等待。所以需确保所有线程完成时都要调用 Signal()方法。
//9)Barrier类
static Barrier bar = new Barrier(2,b => WriteLine($"End of phase {b.CurrentPhaseNumber + 1}"));
/*代码块*/
bar.SignalAndWait();
//实例化指定了我们想要同步两个线程,在两个线程中任何一个调用了 SignalAndWait()方法后,会执行一个回调函数来打印出阶段。
本方法在多线程迭代运算中非常有用,可以在每个迭代结束前执行一些运算,当最后一个线程调用 SignalAndWait()方法时可在迭代结束
时进行交互。
/10)ReaderWriterLockSlim类
static ReaderWriterLockSlim rw = new ReaderWriterLockSlim();
static void Read()
{
while(true)
{
try
{
rw.EnterReadLock();
foreach(...)
{
...读
}
}
finally
{
rw.ExitReadLock();
}
}
}
static void Write()
{
while(true)
{
try
{
rw.EnterUpgradeableReadLock();
if(...)
{
try
{
rw.EnterWriteLock();
...
}
finally
{
rw.ExitWriteLock();
}
}
}
finally
{
rw.ExitUpgradeableReadLock();
}
}
}
//读锁允许多线程读取数据,写锁在释放前会阻塞其他线程的所有操作。一旦得到了写锁,会阻止阅读者读取数据,从而浪费大量的时间,
为了最小化阻塞浪费的时间,使用EnterUpgradeableReadLock()和ExitUpgradeableReadLock()方法。
//11)SpinWait类
4.线程池
1)什么是线程池?
.NET Framework的ThreadPool类提供一个线程池,该线程池可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。
2)为什么要使用线程池?
许多应用程序创建大量处于睡眠状态,等待事件发生的线程。还有许多线程可能会进入休眠状态,这些线程只是为了定期唤醒以轮询更改或更新
的状态信息。 线程池,则可以通过由系统管理的工作线程池来更有效地使用线程。
线程池中的线程执行完指定的方法后并不会自动消除,而是以挂起状态返回线程池,如果应用程序再次向线程池发出请求,那么处以挂起状态的
线程就会被激活并执行任务,而不会创建新线程,这就节约了很多开销。只有当线程数达到最大线程数量,系统才会自动销毁线程。因此,使用
线程池可以避免大量的创建和销毁的开支,具有更好的性能和稳定性,其次,开发人员把线程交给系统管理,可以集中精力处理其他任务。
线程池的优缺点?
优点:
1.避免线程的创建和销毁带来的性能开销。
2.避免大量的线程间因互相抢占系统资源导致的阻塞现象。
3.能够对线程进行简单的管理并提供定时执行、间隔执行等功能。
缺点:ThreadPool不支持线程的取消、完成、失败通知等交互性操作;
ThreadPool不支持线程执行的先后次序;
3)线程池与多线程的区别?
【1】线程池是在程序运行开始,创建好的n个线程,并且这n个线程挂起等待任务的到来。而多线程是在任务到来得时候进行创建,然后执行任务。
【2】线程池中的线程执行完之后不会回收线程,会继续将线程放在等待队列中;多线程程序在每次任务完成之后会回收该线程。
【3】由于线程池中线程是创建好的,所以在效率上相对于多线程会高很多。
【4】线程池也在高并发的情况下有着较好的性能;不容易挂掉。多线程在创建线程数较多的情况下,很容易挂掉。
5.任务并行库
哪些集合类是线程安全的?哪些是不安全?如何解决集合类的线程安全问题?
安全:BlockingCollection<T> ConcurrentBag<T> ConcurrentDictionary<TKey,TValue> ConcurrentQueue<T> ConcurrentStack<T>
不安全:
方法:加锁、
产生死锁的四个必要条件?
互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
Task的使用方式?
1)创建
1.var task = new Task( ()=>
{
for (int i=0;i<500;i++)
{
console.WriteLine(i);
}
});
task.Start();
task.Wait();
task.RunSynchronously();
2.var task = new Task.Factory.StartNew(()=>"1");
3.var task =Task.Run(()=>{});
2)等待
通过wait()对单个task进行等待,Task.waitall()对多个task进行等待,waitany()执行任意一个task就往下继续执行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY