c#之线程同步
1.线程的状态
(1)创建状态
(2)可运行状态
(3)运行状态
(4)阻塞状态
(5)死亡状态
2.线程同步
(1)lock语句
lock语句可以将一段代码定义为互斥段,互斥段在同一时刻内只能有一个线程进入。它的本质是Monitor
示例:有一笔存款,三个人同时在取钱,每个人可取5次。每次取款时,如果数额超过现有的存款,那么取款被拒绝。一个人在取款的时候另一个人不能取,并且存款的额度不能是0.
class Program { static void Main(string[] args) { //创建3个线程 Thread[] threads = new Thread[3]; Account account = new Account(1000); for(int i=0;i<3;i++) { threads[i] = new Thread(account.DoTransactions); threads[i].Name = "Thread " + (i+1); threads[i].Start(); }
//下面的形式也行
Array.ForEach<Thread>(array, t =>
{
t = new Thread(new ThreadStart(Run));
t.Start();
t.Join();
});
} } class Account { private int balance { get; set; } public Account(int initail) { balance = initail; } Random random = new Random(); /// <summary> ///交易 /// </summary> public void DoTransactions() { //在1到1000之间取随机数。 WithDraw(random.Next(1,1000)); } /// <summary> /// 处理 /// </summary> /// <param name="amount"></param> public void WithDraw(int amount) { if(balance<0) { Console.WriteLine(Thread.CurrentThread.Name+"负存款额"); } lock(this) { if(balance>=amount) { Console.WriteLine($"{Thread.CurrentThread.Name}要取出的数额是{amount},现在的存款是{balance}"); balance -= amount; Console.WriteLine($"{Thread.CurrentThread.Name}要取出的数额是{amount},取出后现在的存款是{balance}"); } else { Console.WriteLine($"{Thread.CurrentThread.Name}要取出的数额是{amount},被拒绝"); } } Console.ReadKey(); } }
结果:
Thread 2要取出的数额是227,现在的存款是1000
Thread 2要取出的数额是227,取出后现在的存款是773
Thread 1要取出的数额是295,现在的存款是773
Thread 1要取出的数额是295,取出后现在的存款是478
Thread 3要取出的数额是310,现在的存款是478
Thread 3要取出的数额是310,取出后现在的存款是168
(2)Monitor类
该类通过向单个线程授予对象锁来控制对对象的访问。当一个线程拥有对象锁的时候,其他任何线程都不能获取该锁。
常用的方法:
Enter(),TryEnter():获取对象锁。
Wait():释放对象上的锁以便允许其他线程锁定和访问对象。在其他线程访问对象时,调用线程将等待。
Pulse(),PulseAll():向一个或多个等待线程发送信号。该信号通知等待线程锁定对象的状态已更改并且锁的所有者准备释放该锁。等待线程被放在对象的就绪队列中,以便可以最后接收对象锁,也就是说处于可运行状态。
Exit():释放对象上的锁。
示例:对给定数量的车皮进行调度。有2个调度员,每次调度10个车皮,保证同一时间只有一个调度员在调度。下面的例子保证了2个调度员没人轮一次,一次调度。
class Program { private int ctr = 150; static void Main(string[] args) { // Program account = new Program(); Thread thread = new Thread(new ThreadStart(account.Decrease)); //设置成后台线程。只有IsBackground=TRUE的线程才会随着主线程的退出而退出。 thread.IsBackground = true; thread.Name = "thread"; Thread thread2 = new Thread(new ThreadStart(account.Decrease)); //设置成后台线程。只有IsBackground=TRUE的线程才会随着主线程的退出而退出。 thread2.IsBackground = true; thread2.Name = "thread2"; thread.Start(); thread2.Start(); thread.Join(); thread2.Join(); //所有线程结束 Console.Write("所有线程结束"); Console.ReadKey(); } public void Decrease() { Console.WriteLine($"{Thread.CurrentThread.Name} is running"); while (true) { //获取对象锁 Monitor.Enter(this); if (ctr>=10) { ctr -=10; //模拟做一些其他处理 Thread.Sleep(1000); Console.WriteLine($"{Thread.CurrentThread.Name}调度车皮,还剩{ctr}"); //当前线程调度完车皮后立即让其他线程获取对象锁来调用车皮。 Monitor.Exit(this); } else { Console.WriteLine($"{Thread.CurrentThread.Name}调度失败,数量不够"); Monitor.Exit(this); break; } } } }
结果:
thread2 is running thread is running thread2调度车皮,还剩140 thread调度车皮,还剩130 thread2调度车皮,还剩120 thread调度车皮,还剩110 thread2调度车皮,还剩100 thread调度车皮,还剩90 thread2调度车皮,还剩80 thread调度车皮,还剩70 thread2调度车皮,还剩60 thread调度车皮,还剩50 thread2调度车皮,还剩40 thread调度车皮,还剩30 thread2调度车皮,还剩20 thread调度车皮,还剩10 thread2调度车皮,还剩0 thread调度失败,数量不够 thread2调度失败,数量不够 所有线程结束
其中的Join方法,当线程A调用Join方法的时候,其他线程不会执行(包括主线程),等到线程A执行完毕之后再执行其他线程(当然也可以设置阻塞时间)。
(3)Interlocked类
此类为多个线程共享的变量提供原子操作。
https://www.cnblogs.com/Zachary-Fan/p/interlocked.html 这篇文章写的很好
常用的方法:
// 加1,原子操作,线程安全
Increment( ref Int32 ) ,
Increment( ref Int64 ) ,
// 减1,原子操作,线程安全
Decrement( ref Int32),
Decrement( ref Int64),
// 把第二个参数加到 ref 值上
Add ( ref Int32, Int32 ),
Add ( ref Int64, Int64 ),
// 把第二个参数赋值给 ref 值
Exchange ( ref Int32, Int32 ),
Exchange ( ref Int64, Int64 ),
示例可以在这里看 https://blog.csdn.net/wf824284257/article/details/104055899/
(4)Mutext类
Mutext类用于线程或线程间的同步原语操作。它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要求该互斥体的第二个线程将被挂起,直到第一个线程释放改互斥体。
示例:
class Program { private static Mutex mutex = new Mutex(); private const int numInterations = 1;//使用资源的次数 private const int numThreads = 3;//线程的个数 static void Main(string[] args) { Thread[] threads = new Thread[3]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(MyThread); threads[i].Name = "thread"+i; threads[i].Start(); } } private static void MyThread() { for (int i = 0; i < numInterations; i++) { //使用受保护的资源区 UseResource(); } } private static void UseResource() { //等待mutex对象被释放,如果另一个线程释放之后,则当前线程继续往下执行,后面的线程则在被阻塞在这里等待对象被释放。 Console.WriteLine($"{Thread.CurrentThread.Name}等待进入保护区,时间:{DateTime.Now}"); mutex.WaitOne(); Console.WriteLine($"{Thread.CurrentThread.Name}进入保护区,时间:{DateTime.Now}"); Thread.Sleep(2000); Console.WriteLine($"{Thread.CurrentThread.Name}离开保护区,时间:{DateTime.Now}"); //释放对象互斥体。 mutex.ReleaseMutex(); Console.ReadKey(); } }
结果:
thread2等待进入保护区,时间:2020/7/7 22:52:27 thread0等待进入保护区,时间:2020/7/7 22:52:27 thread2进入保护区,时间:2020/7/7 22:52:27 thread1等待进入保护区,时间:2020/7/7 22:52:27 thread2离开保护区,时间:2020/7/7 22:52:29 thread0进入保护区,时间:2020/7/7 22:52:29 thread0离开保护区,时间:2020/7/7 22:52:31 thread1进入保护区,时间:2020/7/7 22:52:31 thread1离开保护区,时间:2020/7/7 22:52:33
示例2:
static void Main(string[] args) { Thread[] threads = new Thread[3]; for (int i = 0; i < threads.Length; i++) { threads[i] = new Thread(Test); threads[i].Name = "thread"+i; threads[i].Start(); } } private static void Test() { Console.WriteLine($"{Thread.CurrentThread.Name } is Enter,时间{DateTime.Now}"); bool flag = false; System.Threading.Mutex mutex = new System.Threading.Mutex(true, "Test", out flag); //第一个参数:true--给调用线程赋予互斥体的初始所属权。如果一开始没有分线程,则默认主线程。 //第一个参数:互斥体的名称 //第三个参数:返回值,如果调用线程已被授予互斥体的初始所属权,则返回true。 if (flag) { Console.WriteLine($"{Thread.CurrentThread.Name } is Start,时间{DateTime.Now}"); } else { Console.WriteLine($"{Thread.CurrentThread.Name } is Running,时间{DateTime.Now}"); System.Threading.Thread.Sleep(1000);//线程挂起5秒钟 Console.WriteLine($"1秒后: {Thread.CurrentThread.Name } Close,时间{DateTime.Now}"); } Console.ReadLine(); }
结果:
thread1 is Enter,时间2020/7/7 23:05:37 thread2 is Enter,时间2020/7/7 23:05:37 thread0 is Enter,时间2020/7/7 23:05:37 thread1 is Start,时间2020/7/7 23:05:37 thread2 is Running,时间2020/7/7 23:05:37 thread0 is Running,时间2020/7/7 23:05:37 1秒后: thread2 Close,时间2020/7/7 23:05:38 1秒后: thread0 Close,时间2020/7/7 23:05:38