在阿里云上对互斥量的概述:互斥量的获取是完全互斥的,即同一时刻,互斥量只能被一个任务获取。而信号量按照起始的计数值的配置,可以存在多个任务获取同一信号量的情况,直到计数值减为0,则后续任务无法再获取信号量,当信号量的计数初值设置为1,同样有互斥的效果。但信号量无法避免优先级反转问题。
注意事项:
⑴ 互斥量只能由获取该互斥量的任务的释放,不能由其他任务释放。
⑵ 互斥量已被当前任务获取,若当前任务再次获取互斥量则返回错误。
微软官方文档的解释因为加了很多的名词,看起来解释得深入,实际上有点绕。但是看代码就好理解一些。
前面的文章《在C#中使用信号量解决多线程访问共享资源的冲突问题》,可能看过的就明白为什么使用信号量,就是限制同步数,任何时刻只有一个线程对资源的操作,这样肯定不会发生冲突,但是这样会限制了性能。
信号量就是限制同步线程的数量,解决多线程对共享资源可能产生的冲突问题,可能还是使用锁、原子操作或者互斥量比较正规一些。
1、互斥量的简单使用
问题:两个任务同时执行,每个任务都产生1到10的随机数,最后统计所产生的1到10的数字个数。
实现代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics.Metrics;
namespace MultiThread20230224
{
public partial class Form2 : Form
{
public static int[] PSPArr = new int[11];
public int ExecCount = 0;
public static Mutex mutex = new Mutex(); // 创建互斥量
public Form2()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
private void button1_Click(object sender, EventArgs e)
{
for(int i=0; i<PSPArr.Length;i++)
{
PSPArr[i] = 0;
}
ExecCount = int.Parse(textBox4.Text);
if( ExecCount == 0 || ExecCount==null) {
ExecCount= 10;
}
DateTime start = DateTime.Now;
SubTask ST1 = new SubTask(1);
SubTask ST2 = new SubTask(2);
Thread t1 = new Thread(ST1.DoTask);
Thread t2 = new Thread(ST2.DoTask);
t1.Start(ExecCount);
t2.Start(ExecCount);
t1.Join();
t2.Join();
DateTime end = DateTime.Now;
TimeSpan tspan = end - start;
string time =((int)tspan.TotalMilliseconds).ToString();
textBox1.Text = "";
textBox2.Text = "";
textBox3.Text = "";
//显示统计结果
for (int i=1;i<11;i++)
{
string S1 = "×";
textBox1.Text += i.ToString() + " ==> " + ST1.Arr[i] + Environment.NewLine;
textBox2.Text += i.ToString() + " ==> " + ST2.Arr[i] + Environment.NewLine;
if (PSPArr[i]== ST1.Arr[i]+ ST2.Arr[i])
{
S1 = "√";
}
textBox3.Text += i.ToString() + " ==> " + PSPArr[i] +" "+S1+ Environment.NewLine;
}
label6.Text= time.ToString();
}
}
public class SubTask{
string TaskName = "";
public int[] Arr=new int[11];
public SubTask(int TaskNum) {
TaskName = "任务"+TaskNum.ToString();
}
public void DoTask(object obj)
{
int ii = (int)obj;
for (int i = 0; i < ii; i++){
int num = new Random().Next(1, 11);
Arr[num] += 1;//本地计数
// 加锁,防止多个线程同时修改counts数组
Form2.mutex.WaitOne();
Form2.PSPArr[num] +=1;
Form2.mutex.ReleaseMutex();
}
}
}
}
显示结果:
2、信号量与互斥量的结合使用
与上面的问题相似,启动100个任务,每个任务产生一个1到10的随机数,最后统计所产生的1到10的数字个数。
实现代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MultiThread20230224
{
public partial class Form3 : Form
{
static SemaphoreSlim sem = new SemaphoreSlim(3);
static Mutex mutex = new Mutex();
static int[] Arr = new int[11];
static Random random = new Random();
List<Thread> threads = new List<Thread>();
public Form3()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 1; i < 11; i++)
{
Arr[i]=0;
}
textBox1.Text= "";
DateTime start = DateTime.Now;
threads.Clear();
for (int i = 0; i < 100; i++)
{
Thread t = new Thread(new ThreadStart(Task));
threads.Add(t);
}
foreach (Thread t in threads)
{
t.Start();
}
foreach (Thread t in threads)
{
t.Join();
}
DateTime end = DateTime.Now;
TimeSpan tspan = end - start;
string time = ((int)tspan.TotalMilliseconds).ToString();
//显示统计结果
int ITemp = 0;
for (int i = 1; i < 11; i++)
{
ITemp += Arr[i];
textBox1.Text += i.ToString() + " ==> " + Arr[i] + Environment.NewLine;
}
textBox1.Text += " 总数 ==> " + ITemp.ToString() + Environment.NewLine;
textBox1.Text += "耗时 ==> " + time.ToString()+" 毫秒";
}
static void Task()
{
sem.Wait();
int num = random.Next(1, 11);
mutex.WaitOne();
Arr[num]++;
mutex.ReleaseMutex();
sem.Release();
}
}
}
显示结果:
上面的程序信号量用于限制线程的同步数,互斥量用于限制同时访问共享资源,保证不发生冲突。
如果为了测试信号量的大小以及生成随机数的个数大小对程序执行时间的影响,可是改变sem的大小,同时改变Task方法。
改变sem:
int semaphoreCount = Convert.ToInt32(textBox3.Text);
sem = new SemaphoreSlim(semaphoreCount);
改变Task方法:
static void Task(object count)
{
sem.Wait();
int num = random.Next(1, 11);
mutex.WaitOne();
Arr[num]++;
mutex.ReleaseMutex();
sem.Release((int)count); // 释放指定数量的信号量
}
改变线程的启动:
foreach (Thread t in threads)
{
t.Start(10); //将产生随机数的个数作为参数传入
}
信号量的个数大小对程序的执行快慢有一定影响。一方面,如果信号量的个数较小,可能会导致线程需要等待的时间较长,从而降低程序的执行速度。另一方面,如果信号量的个数过多,会导致操作系统需要维护的信号量数量过多,也会增加程序的开销和系统负担。一般来说,合理设置信号量的个数可以提高程序的执行效率。根据实际需求,可以进行性能测试,不断调整信号量的个数,以达到最优的执行效果。
信号量的作用是控制同一时间可执行的线程数量。
互斥量的作用是确保同一时间只有一个线程访问共享资源。
相关文章:
⑴ C#线程的参数传递、获取线程返回值以及处理多线程冲突
⑵ 在C#中使用信号量解决多线程访问共享资源的冲突问题