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

news2024/11/25 2:53:45

  目前在我写的233篇原创文章中,有两篇是粉丝可见的,其中《C#线程的参数传递、获取线程返回值以及处理多线程冲突》这篇文章有179个粉丝关注,看到不断有人关注这篇文章,这表明学习C#的人还是挺多的,感觉文章内容不够厚实,对不起粉丝的关注,加上文章末尾说了要写一篇详细的多线程通讯,今天就写了使用信号量来解决多线程访问共享资源可能导致的冲突或者错误。
  解决这样的问题还有很多手段,比如可以使用锁、自旋锁、事件、管道、互斥量、原子操作等等,而不仅仅是只有使用信号量这一手段。
  如果后面再使用C#,就写其他的,这一篇主要涉及信号量,包括使用信号量解决多线程访问共享资源的冲突问题以及在线程池中使用信号量。
  目录:
  1、问题:有两个任务同时进行,它们的任务内容都是在10秒内每隔一秒随机产生一个1到10的随机数,第三个任务随时统计并显示两个任务所产生1~10数字的个数。
  2、问题:有两个任务同时进行,它们的任务内容都是随机产生1000个一个1到10的随机数,第三个任务统计并显示两个任务所产生1~10数字的个数。
  3、使用锁来解决多线程访问共享资源的冲突(最常见的做法)。
  4、使用信号量解决多线程访问共享资源所可能产生的冲突问题。
  5、使用线程池与信号量解决多线程访问共享资源可能导致的冲突问题。

  在C#中,信号量(Semaphore)是一种同步原语,它可以用来控制多个线程对共享资源的访问。信号量维护了一个计数器,当有线程访问共享资源时,计数器减1;当线程释放共享资源时,计数器加1。如果计数器为0,表示没有可用的资源,此时线程需要等待,直到有其它线程释放资源。
  信号量的主要作用是实现对共享资源的控制和同步,以避免多个线程同时访问共享资源而导致的冲突。通过使用信号量,我们可以确保同一时间只有指定数量的线程能够访问共享资源,从而避免冲突。除了线程管理之外,信号量还可以应用于进程管理、网络编程、并发控制等领域。
  在实际开发中,如果需要协调对共享资源的访问,避免多个线程或进程同时对共享资源进行操作,信号量是一个很有用的工具。
  信号量其实是一种操作系统的原语,它不仅应用在C#中,其他的编程语言或者操作系统也有它的实现。在操作系统中,信号量主要用于进程间的同步和通信,它可以用来协调对共享资源的访问,避免多个进程同时对共享资源进行操作导致的冲突。

  1问题:有两个任务同时进行,它们的任务内容都是在10秒内每隔一秒随机产生一个1到10的随机数,第三个任务随时统计并显示两个任务所产生1~10数字的个数。

  这个问题实现简单,代码如下:

using System;
using System.Threading;

namespace MultiThread20230224
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            int[] Arr = new int[11];

            // 创建线程
            Thread t1 = new Thread(() => {
                // 对共享资源的操作
                textBox1.Text = "";
                int count = 0;
                while (count++ < 10)
                {
                    Random r = new Random();
                    int number = r.Next(1, 11);
                    textBox1.Text = textBox1.Text + number.ToString() + Environment.NewLine;
                    Arr[number] ++ ;
                    Thread.Sleep(1000);
                }
            });
            t1.Start();

            Thread t2 = new Thread(() => {
                textBox2.Text = "";
                // 对共享资源的操作
                int count = 0;
                while (count++ < 10)
                {
                    Random r = new Random();
                    int number = r.Next(1, 11);
                    textBox2.Text = textBox2.Text + number.ToString() + Environment.NewLine;
                    Arr[number]++;
                    Thread.Sleep(1000);
                }
            });
            t2.Start();

            Thread t3 = new Thread(() => {
                // 显示统计数据
                int count = 0;
                while (count++ < 10)
                {
                    textBox3.Text = "";
                    for (int i = 1; i < Arr.GetLength(0); i++)
                    {
                        textBox3.Text = textBox3.Text +i.ToString()+" ==> "+Arr[i].ToString()+ Environment.NewLine;
                    }
                    Thread.Sleep(1000);
                }
            });
            t3.Start();
        }
    }
}

  运行后发现最后结果是正确的,程序也没有报告错误,哪怕运行多次也是这样,但是代码中没有使用任何线程同步机制来确保线程安全性,因此在多次运行时,可能会产生意外的结果,比如有些数字没有被计算,或者计数器的值不准确等。
  为了验证多线程对共享资源访问可能发生的冲突或者错误,将需求更改为:
  2、问题:有两个任务同时进行,它们的任务内容都是随机产生1000个一个1到10的随机数,第三个任务统计并显示两个任务所产生1~10数字的个数。

  实现代码如下:

using System;
using System.Threading;

namespace MultiThread20230224
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            int[] Arr = new int[11];
            int[] Arr1 = new int[11];//记录任务一的1到10所产生的个数统计
            int[] Arr2 = new int[11];//记录任务二的1到10所产生的个数统计

            // 创建线程
            Thread t1 = new Thread(() => {
                textBox1.Text = "";
                int count = 0;
                while (count++ < 1000)
                {
                    //semaphore.WaitOne();
                    Random r = new Random();
                    int number = r.Next(1, 11);
                    //textBox1.Text = textBox1.Text + number.ToString() + Environment.NewLine;
                    Arr[number] ++ ;
                    Arr1[number]++;
                    //Thread.Sleep(1);
                    //semaphore.Release();
                }
            });
            t1.Start();

            Thread t2 = new Thread(() => {
                textBox2.Text = "";
                int count = 0;
                while (count++ < 1000)
                {
                    //semaphore.WaitOne();
                    Random r = new Random();
                    int number = r.Next(1, 11);
                    //textBox2.Text = textBox2.Text + number.ToString() + Environment.NewLine;
                    Arr[number]++;
                    Arr2[number]++;
                    //Thread.Sleep(1);
                    //semaphore.Release();
                }
            });
            t2.Start();
            t1.Join();
            t2.Join();

            Thread t3 = new Thread(() => {
                    textBox1.Text = "";
                    textBox2.Text = "";
                    textBox3.Text = "";
                    for (int i = 1; i < Arr.GetLength(0); i++)
                    {
                        textBox1.Text = textBox1.Text + i.ToString() + " ==> " + Arr1[i].ToString() + Environment.NewLine;
                        textBox2.Text = textBox2.Text +i.ToString()+" ==> "+Arr2[i].ToString()+ Environment.NewLine;
                        textBox3.Text = textBox3.Text + i.ToString() + " ==> " + Arr[i].ToString() + Environment.NewLine;
                    }
            });
            t3.Start();
        }
    }
}

  所产生的结果显示:

  可以看到结果有很多错误。这就表明了多线程访问的数据竞争问题。数据竞争可能会导致不可预测的结果,例如应用程序崩溃或不正确的行为、结果。
  3、使用锁来解决多线程访问共享资源的冲突(最常见的做法)

using System;
using System.Threading;

namespace MultiThread20230224
{
    public partial class Form1 : Form
    {
        private object lockObj = new object();
        int[] Arr = new int[11];
        int[] Arr1 = new int[11];
        int[] Arr2 = new int[11];
        public Form1()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread t1 = new Thread(() => {
                int count = 0;
                while (count++ < 1000)
                {
                    Random r = new Random();
                    int number = r.Next(1, 11);
                    lock (lockObj)
                    {
                        Arr[number]++;
                    }                        
                    Arr1[number]++;
                }
            });
            t1.Start();

            Thread t2 = new Thread(() => {
                int count = 0;
                while (count++ < 1000)
                {
                    Random r = new Random();
                    int number = r.Next(1, 11);
                    lock (lockObj)
                    {
                        Arr[number]++;
                    }
                    Arr2[number]++;
                }
            });
            t2.Start();
            t1.Join();
            t2.Join();

            Thread t3 = new Thread(() => {
                    textBox1.Text = "";
                    textBox2.Text = "";
                    textBox3.Text = "";
                    for (int i = 1; i < Arr.GetLength(0); i++)
                    {
                        textBox1.Text = textBox1.Text + i.ToString() + " ==> " + Arr1[i].ToString() + Environment.NewLine;
                        textBox2.Text = textBox2.Text +i.ToString()+" ==> "+Arr2[i].ToString()+ Environment.NewLine;
                        textBox3.Text = textBox3.Text + i.ToString() + " ==> " + Arr[i].ToString() + Environment.NewLine;
                    }
            });
            t3.Start();
        }
    }
}

  现在的结果就是正确的。

   4、使用信号量解决多线程访问共享资源所可能产生的冲突问题

  先看实现的代码:

using System;
using System.Threading;

namespace MultiThread20230224
{
    public partial class Form1 : Form
    {
        //private object lockObj = new object();
        int[] Arr = new int[11];//记录两个任务所产生的1到10的个数统计
        int[] Arr1 = new int[11];//记录任务一的1到10所产生的个数统计
        int[] Arr2 = new int[11];//记录任务二的1到10所产生的个数统计
        private SemaphoreSlim semaphore = new SemaphoreSlim(1);  // 声明一个信号量对象

        public Form1()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 创建线程
            Thread t1 = new Thread(() => {
                // 对共享资源的操作
                int count = 0;
                while (count++ < 1000)
                {
                    semaphore.Wait();  // 请求信号量
                    int number = new Random().Next(1, 11);
                    Arr[number]++;
                    Arr1[number]++;
                    semaphore.Release();  // 释放信号量
                }
            });
            t1.Start();

            Thread t2 = new Thread(() => {
                // 对共享资源的操作
                int count = 0;
                while (count++ < 1000)
                {
                    semaphore.Wait();  // 请求信号量
                    int number = new Random().Next(1, 11);
                    Arr[number]++;
                    Arr2[number]++;
                    semaphore.Release();  // 释放信号量
                }
            });
            t2.Start();
            t1.Join();
            t2.Join();

            Thread t3 = new Thread(() => {
                // 显示统计数据
                    string S1 = "";
                    textBox1.Text = "";
                    textBox2.Text = "";
                    textBox3.Text = "";
                    for (int i = 1; i < Arr.GetLength(0); i++)
                    {
                        textBox1.Text = textBox1.Text + i.ToString() + " ==> " + Arr1[i].ToString() + Environment.NewLine;
                        textBox2.Text = textBox2.Text +i.ToString()+" ==> "+Arr2[i].ToString()+ Environment.NewLine;
                        if (Arr[i]== Arr1[i]+ Arr2[i])
                        {
                            S1 = "√";
                        }else{
                            S1 = "×";
                        }
                        textBox3.Text = textBox3.Text + i.ToString() + " ==> " + Arr[i].ToString()+" "+S1 + Environment.NewLine;
                    }
            });
            t3.Start();
        }
    }
}

  结果显示:

  上面的实现比较简单,可以改动程序:
  声明信号量:

static Semaphore semaphore = new Semaphore(1, 1);

  改写等待信号语句:

semaphore.WaitOne();

  出来的结果也是一样的正确。
  说明:
  ⑴ 在使用 SemaphoreSlim 时,构造函数中的参数表示信号量的初始计数。0 表示信号量一开始没有可用的许可证,需要等待另一个线程调用 Release 方法来增加计数并释放许可证。如果初始值为1或更高,则表示初始情况下有可用的许可证,其他线程可以直接调用 Wait 方法并获得许可证而不必等待。
  ⑵ 在使用SemaphoreSlim的情况下,通过Wait()和Release()方法来控制线程的同步。
  ⑶ SemaphoreSlim 和 Semaphore 都是用来控制多个线程对共享资源的访问的工具,但它们的实现方式不同,有一些细微的差别。SemaphoreSlim 是一个轻量级的 Semaphore 实现,与 Semaphore 相比,它更快、更节省资源。 
  ⑷ Semaphore(1, 1) 是 Semaphore 的一个构造函数,它创建了一个初始计数为1、最大计数为1的信号量。这意味着在任何时刻只能有一个线程获得该信号量并访问共享资源。 

   延续上面的问题,如果是100个这样的任务同时进行,显然,代码就不可能这样写,需要使用线程池来解决了。
  问题:有100个任务同时进行,它们的任务内容都是随机产生一个1到10的随机数,第三个任务统计并显示两个任务所产生1~10数字的个数。

   5、使用线程池与信号量解决多线程访问共享资源可能导致的冲突问题

  实现代码:

using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace MultiThread20230224
{
    public partial class Form1 : Form
    {
        static int[] Arr = new int[11];
        static SemaphoreSlim semaphore = new SemaphoreSlim(1); // 用于保证对Arr数组的操作是线程安全的

        public Form1()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 启动100个任务
            for (int i = 0; i < 100; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoTask), i);
            }

            // 等待所有任务完成
            Thread.Sleep(5000);

            textBox3.Text = "";
            int count = 0;
            for (int i = 1; i < 11; i++)
            {
                count += Arr[i];
                textBox3.Text = textBox3.Text + i.ToString() + " ==> " + Arr[i].ToString() + Environment.NewLine;
            }
            textBox3.Text = textBox3.Text + "总数 ==> " + count.ToString() + Environment.NewLine;
        }

        static void DoTask(object TaskNum)
        {
            int index = (int)TaskNum;
            Random rand = new Random();
            for (int i = 0; i < 10; i++)
            {
                //这里可以记录每个任务(index)所产生的数据,这里忽略
                int number = rand.Next(1, 11); // 产生一个1~10之间的随机数
                // 使用信号量保证对 count 数组的操作是线程安全的
                semaphore.Wait();
                Arr[number]++;
                semaphore.Release();
            }
        }
    }
}

  显示结果:(因为每个线程里产生10个数,100个线程应该产生1000个数,而程序中的总数是累加了各个线程所产生的个数总计,所以也应该是1000才对)

   上面的代码中同时创建了 100 个线程,并将它们全部加入到线程池中。然后,使用线程池的 QueueUserWorkItem 方法将 100 个任务分配给这 100 个线程去执行。使用semaphore.Wait()和semaphore.Release()保证对Arr数组的访问不发生冲突。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373716.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

泛型<E>

泛型 案例引出泛型 按要求写出代码&#xff1a; 在ArrayList中添加3个Dog对象&#xff0c;Dog对象有name和age两个属性&#xff0c;且输出name和age public class test1 {public static void main(String[] args) {ArrayList list new ArrayList();list.add(new Dog(10,&quo…

Python解题 - CSDN周赛第32期 - 运输石油(三维背包)

上期周赛因为最后一题出现bug&#xff0c;再加上都是经典的模板题&#xff0c;问哥就懒得写题解了。 本期也是有两道考过的题目&#xff0c;不过最后一题因为考到了背包问题的特殊类型&#xff0c;还是值得拿出来记个笔记。 第一题&#xff1a;传奇霸业 传奇霸业&#xff0c;是…

Unity高程图生成

序大概就是根据一个灰度图&#xff0c;生成一个地形。分两步来实现吧&#xff1b;首先&#xff0c;用随机数生成地形&#xff1b;然后&#xff0c;根据灰度图生成地形。小白&#xff0c;没啥基础&#xff0c;所以只能慢慢来。参考&#xff1a;【萌新图形学】地形网格生成入门 含…

基于stm32电梯管理系统设计

基于stm32电梯管理系统设计这里记录一下以前自己做的嵌入式课程设计&#xff0c;报告中的图片和文字太多了&#xff0c;全部一个一个把搬过来太麻烦了,需要完整文本和代码自行q我963160156&#xff0c;也可在微信公众号 *高级嵌入式软件* 里回复 *电梯* 查看完整版文章摘要关键…

Oracle Apex 21.2 安装过程

什么是 Oracle APEX&#xff1f; Oracle APEX 是广受欢迎的企业级低代码应用平台。借助该平台&#xff0c;您可以构建功能先进的可扩展安全企业应用&#xff0c;并在任何位置&#xff08;云或内部部署&#xff09;部署这些应用。 使用 APEX&#xff0c;开发人员可快速开发并部…

域组策略自动更新实验报告

域组策略自动更新实验报告 域组策略自动更新实验报告 作者: 高兴源 1要求、我公司为了完善员工的安全性和系统正常漏洞的维护&#xff0c;所以采用域组策略自动更新的方法来提高账户安全性&#xff0c;减少了用户的错误。 1.实验环境如下1台2008r2一台创建域&#xff0c;一台wi…

【云原生】k8s中Pod进阶资源限制与探针

一、Pod 进阶 1、资源限制 当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小&#xff0c;以及其他类型的资源。 当为 Pod 中的容器指定了 request 资源时&#xff0c;调度器就使用该信息来决定将 Pod 调度到哪个节点上。当还…

嵌入式 STM32 步进电机驱动,干货满满,建议收藏

目录 步进电机 1、步进电机驱动原理 2、步进电机驱动 3、步进电机应用 1、第一步&#xff1a;初始化IO口 2、设置行进方式 四、源码 步进电机 步进电机被广泛应用于ATM机、喷绘机、刻字机、写真机、喷涂设备、医疗仪器及设备、计算机外设及海量存储设备、精密仪器、工业…

_improve-3

createElement过程 React.createElement()&#xff1a; 根据指定的第一个参数创建一个React元素 React.createElement(type,[props],[...children] )第一个参数是必填&#xff0c;传入的是似HTML标签名称&#xff0c;eg: ul, li第二个参数是选填&#xff0c;表示的是属性&#…

String、StringBuffer和StringBuilder的详解

目录 一、String讲解 1.String&#xff08;String字符串常量&#xff09; 2.String 拼接方式与性能的影响 二、StringBuffer 和 StringBuilder 讲解 1.StringBuffer 和 StringBuilder 使用场景:(StringBuffer、StringBuilder字符串变量) 2.StringBuffer的使用 3.StringB…

shell脚本常用命令

shell概述 shell是一个命令行解释器&#xff0c;它接收应用程序/用户命令&#xff0c;然后调用操作系统内核。 shell还是一个功能强大的编程语言&#xff0c;易编写、易调试、灵活性强。 shell解析器 查看系统自带的所有shell解析器 cat /etc/shells查看系统默认的shell解析…

超算中心、并行计算

现在超算中心已经迅速发展 合肥&#xff1a; 合肥先进中心 合肥曙光超算中心平台 合肥安徽大学超算中心 合肥中科大超算中心 合肥中科院超算中心 合肥大一点的公司都会有自己的集群&#xff0c; 超算中心又称为集群&#xff0c;一般集群是小型服务器组成&#xff0c;超…

EasyRecovery16免费的电脑的数据恢复工具

常见的数据恢复有两种方式&#xff0c;第一种方式是找别人恢复&#xff0c;按照市场价来说&#xff0c;数据恢复的价格每次在100-500之间&#xff0c;但这种方式容易使自己设备上的隐私资料泄露出去&#xff0c;不安全。 另一种方式则是自己学会数据恢复的方法&#xff0c;有问…

逻辑回归

逻辑回归 在分类问题中&#xff0c;要预测的变量y为离散值&#xff08;y0~1&#xff09;&#xff0c;逻辑回归模型的输出变量范围始终在 0 和 1 之间。 训练集为 {(x(1),y(1)),(x(2),y(2)),...,(x(m),y(m))}\{(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),...,(x^{(m)},y^{(m)})\} {…

地址,指针,指针变量是什么?他们的区别?符号(*)在不同位置的解释?

指针是C语言中的一个重要概念&#xff0c;也是C语言的一个重要特色&#xff1b;使用指针&#xff0c;可以使程序简洁、紧凑、高效。不掌握指针&#xff0c;就没有掌握C语言的精华。 目录 一、定义 1.1地址 1.2指针 1.3指针变量 1.4指针和指针变量的区别 二、使用指针变量…

C#关于HWindowControl实现一些便捷功能——(缩放与拖动图像)

C#关于HWindowControl实现一些便捷功能——&#xff08;缩放与拖动图像&#xff09;一、关于Hwindow窗体显示的part二、以鼠标为中心的缩放三、以鼠标拖动移动图片一、关于Hwindow窗体显示的part 首先 HWindowControl 控件的尺寸是固定的&#xff0c;当我们在这个固定的尺寸中…

C++类和对象:构造函数和析构函数

目录 一. 类的六个默认成员函数 二. 构造函数 2.1 什么是构造函数 2.2 编译器自动生成的默认构造函数 2.3 构造函数的特性总结 三. 析构函数 3.1 什么是析构函数 3.2 编译器自动生成的析构函数 3.3 析构函数的特性总结 一. 类的六个默认成员函数 对于任意一个C类&…

零基础如何入门网络安全(黑客)

我经常会看到这一类的问题&#xff1a; 学习XXX知识没效果&#xff1b;学习XXX技能没方向&#xff1b;学习XXX没办法入门&#xff1b; 给大家一个忠告&#xff0c;如果你完全没有基础的话&#xff0c;前期最好不要盲目去找资料学习&#xff0c;因为大部分人把资料收集好之后&a…

三天吃透Java虚拟机面试八股文

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

趣味数学题存疑待证1

原文出自&#xff1a;球面上随机 N 个点在同一个半球上的概率 要求任意N个点&#xff0c;全在同一个半球上的概率&#xff0c;我们需要构造使得分母为有限的样本集合&#xff0c;分子则为有N个点在同一半球的情况集 首先对任意N个点取其对称点使得可划分点为2N&#xff0c;在…