在C#中使用互斥量解决多线程访问共享资源的冲突问题

  在阿里云上对互斥量的概述:互斥量的获取是完全互斥的,即同一时刻,互斥量只能被一个任务获取。而信号量按照起始的计数值的配置,可以存在多个任务获取同一信号量的情况,直到计数值减为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#中使用信号量解决多线程访问共享资源的冲突问题

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在多线程编程资源共享是一个常见的问题。当多个线程同时访问和修改共享资源时,如果没有正确的同步机制,就会出现数据竞争和不可预测的结果。以下是一些处理多线资源共享问题的常用方法: 1. 使用互斥锁(Mutex)或锁(lock):通过在访问共享资源的代码块上加锁,确保同一时间只有一个线程可以访问资源。这样可以避免数据竞争和并发修改的问题。 ```csharp private static readonly object lockObject = new object(); lock (lockObject) { // 访问共享资源的代码 } ``` 2. 使用线程安全的集合类:在C#,有一些线程安全的集合类,例如`ConcurrentQueue`、`ConcurrentStack`、`ConcurrentDictionary`等。它们内部实现了适当的同步机制,可以在多线程环境下安全地进行读写操作。 ```csharp ConcurrentQueue<int> queue = new ConcurrentQueue<int>(); // 线程1往队列添加元素 queue.Enqueue(1); // 线程2从队列取出元素 int item; if (queue.TryDequeue(out item)) { // 处理取出的元素 } ``` 3. 使用互斥体(Monitor):通过使用`Monitor`类来创建临界区,确保只有一个线程可以进入临界区访问共享资源。 ```csharp private static readonly object lockObject = new object(); Monitor.Enter(lockObject); try { // 访问共享资源的代码 } finally { Monitor.Exit(lockObject); } ``` 4. 使用原子操作:C#提供了一些原子操作的方法,例如`Interlocked`类的方法,可以在多线程环境下进行原子性的读写操作,避免数据竞争和并发修改的问题。 ```csharp private static int counter = 0; Interlocked.Increment(ref counter); // 原子性地增加计数器 ``` 以上方法可以帮助你处理多线资源共享问题,确保线程安全和数据一致性。根据具体情况选择合适的方法来处理资源共享,以满足程序的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值