惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
H
Hacker News: Front Page
N
News and Events Feed by Topic
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
K
Kaspersky official blog
Forbes - Security
Forbes - Security
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
P
Privacy & Cybersecurity Law Blog
H
Heimdal Security Blog
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
V
Vulnerabilities – Threatpost
T
Tenable Blog
T
Tailwind CSS Blog
P
Privacy International News Feed
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
博客园 - Franky
Hacker News: Ask HN
Hacker News: Ask HN
Jina AI
Jina AI
C
Cybersecurity and Infrastructure Security Agency CISA
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
Vercel News
Vercel News
A
About on SuperTechFans
爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
The Last Watchdog
The Last Watchdog
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
量子位
博客园 - 三生石上(FineUI控件)
J
Java Code Geeks
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Martin Fowler
Martin Fowler
Project Zero
Project Zero

博客园 - 老码识途呀

基于.NET的Windows窗体编程之WinForms图像控件 基于.NET的Windows窗体编程之WinForms提示控件 基于.NET的Windows窗体编程之WinForms时间控件 基于.NET的Windows窗体编程之WinForms图表控件 基于.NET的Windows窗体编程之WinForms输入与显示 基于.NET的Windows窗体编程之WinForms菜单工具栏 基于.NET的Windows窗体编程之WinForms打印 基于.NET的Windows窗体编程之WinForms对话框 基于.NET的Windows窗体编程之WinForms列表控件 基于.NET的Windows窗体编程之WinForms数据表格 基于.NET的Windows窗体编程之WinForms事件简介 基于.NET的Windows窗体编程之WinForms布局简介 基于.NET的Windows窗体编程之WinForms控件简介 基于.NET的Windows窗体编程之WinForms入门简介 推荐一款基于.NET和百度飞桨的OCR识别组件 新书速递,手把手教你WPF入门与开发 推荐一种手动设置异步线程等待机制的解决方案 推荐一种异步线程执行过程中更新进度的方法 推荐一款基于.NET的进程间数据交互经典解决方案 推荐一款线程or进程间数据同步解决方案 推荐一款代码规范检查(CodeReview)神器 推荐一款高性能状态机管理解决方案 推荐一款进程间高速交换数据的解决方案 推荐一款基于.NET的进程间通信框架 推荐一款基于EF-Core的分库分表利器 推荐一种程序崩溃闪退或卡死的分析方法 推荐一款Excel读写神器之MiniExcel入门详解 推荐一款Ioc容器之Ninject入门详解 ASP.NET Core WebApi+React UI开发入门详解
推荐一种并发线程中资源同步常用方法
老码识途呀 · 2025-12-14 · via 博客园 - 老码识途呀

在实际应用开发中,为了提高效率,一些大的任务会被拆成很多小的子任务,然后再将任务按照先后顺序进行排列组合,而某些可以同时执行的任务,就会被安排成并行执行,进而就会用到多线程去处理;这些并发线程,有时会需要使用同一种资源,且这种资源在同一时刻也只能供少量或单一线程使用,这种资源被称为临界资源。那如何才能保证在并发线程中,各个线程都能有条不紊的使用临界资源呢?我们需要给临界资源一个信号量(Semaphore),当资源正在被占用时,告诉其他后面的线程,需要等待前面的线程使用资源结束,才能接着使用,总而言之,就是需要排队使用资源。

今天我们以一些简单的小例子,简述在.NET开发中信号量的使用方法与应用,仅供学习分享使用,如有不足之处,还请指正。

image

什么是信号量?

在 C# 中,信号量(Semaphore)是一种用于线程同步的机制,能够控制对共享资源的访问。它的工作原理是通过维护一个计数器来控制对资源的访问次数。它常用于限制对共享资源(如数据库连接池、文件系统、网络资源等)的并发访问。

信号量理解起来有三点核心要素:

  1. 计数:信号量的核心是一个计数器,表示当前资源的可用数量;即总资源数-正在被使用资源数=剩余可用数量。
  2. 等待:当线程请求资源时,此次如果计数器大于0,则线程可以继续执行,同时计数器减1;如果计数器等于0,则线程被阻塞直至其他线程释放资源,即有线程增加计数器的值;
  3. 释放:当线程使用完资源后,则需要释放信号量,同时计数器加1,并唤醒其他等待的线程;

通过信号量的定义和核心要素,以及实现原理,可以推断出它的应用场景,就是控制对共享临界资源的使用,避免出现过度使用资源的现象出现。

在.NET开发中,基于C#语言,有两种信号量实现方式,具体如下所示:

  • Semaphore:是基于系统内核实现,属于内核级别同步,支持跨进程资源同步,因此性能较低,内存占用较大;它可以一次释放多个信号量,但是没有提供原生的异步支持;
  • SemaphoreSlim:是用户级别同步,并不依赖系统内核,因此不支持跨进程资源同步,因此性能更高,内存占用更低;它一次只能释放一个信号量,但是提供了原生异步支持;

具体对比如下图所示:

image

Semaphore实例

Semaphore主要方法有以下几个:

  • 构造方法,Semaphore提供在构造方法有3个,可以根据需要用来设置初始信号数量,最大信号数量,信号量名称,out修饰的是否创建新的信号。如果不需要跨进程,则采集两个默认两个参数在构造函数即可。
  • WaitOne,等待方法用于等待一个信号,此方法接收一个参数用于设置超时时间,它返回一个bool值,true表示接收到信号,false表示没有接收到信号。
  • Release,释放资源方法,默认释放一个资源的占用。此方法接收一个参数用于设置释放占用资源的数量。

应用示例:定义一个共享资源信号量,同一时刻允许2个线程使用,最大线程数量为5,则采用默认构造函数并指定初始值,最大值即可,如下所示:

namespace Okcoder.DemoSemaphore
{
    public class Test
    {
        private readonly Semaphore semaphore;

        public Test()
        {
            semaphore = new Semaphore(2, 5);
        }

        /// <summary>
        /// Work
        /// </summary>
        /// <param name="id"></param>
        public void Work(int id)
        {
            try
            {
                Console.WriteLine($"id = {id} 正在等待进入 working area.");
                semaphore.WaitOne();
                Console.WriteLine($"id = {id} 进入了working area.");
                //do something
                Thread.Sleep(3000);
            }
            catch
            {
                throw;
            }
            finally
            {
                Console.WriteLine($"id = {id} 离开了working area.");
                semaphore.Release();
            }
        }
    }
}

当我们创建5个线程,同时调用资源时,也只允许同一时刻2个线程使用,如下所示:

using Okcoder.DemoSemaphore;
using System;

namespace ConsoleAppWithoutTopLevelStatements
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            Test test = new Test();
            for (int i = 0; i < 5; i++)
            {
                int id = i;
                Task.Run(() =>
                {
                    test.Work(id);
                });
            }
            Console.ReadKey();
        }
    }
}

运行实例,效果如下所示:

image

命名信号量

Semaphore是内核级别同步,在构造函数中,可以为信号量指定名称,这样就可以跨进程获取信号量,并进行操作。如下所示:

namespace Okcoder.DemoSemaphore
{
    public class Test3
    {
        private readonly Semaphore semaphore;

        public Test3()
        {
            semaphore = new Semaphore(1, 1,"okcoder");
        }

        /// <summary>
        /// Work
        /// </summary>
        /// <param name="id"></param>
        public void Work(int id)
        {
            Console.WriteLine($"id = {id} 正在等待进入 working area.");
            semaphore.WaitOne();
            Console.WriteLine($"id = {id} 进入了working area.");
        }
    }
}

同样运行5个线程调用方法,如下所示:

using Okcoder.DemoSemaphore;
using System;

namespace ConsoleAppWithoutTopLevelStatements
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            Test3 test = new Test3();
            for (int i = 0; i < 5; i++)
            {
                int id = i;
                Task.Run(() =>
                {
                    test.Work(id);
                });
            }
            Console.ReadKey();
        }
    }
}

在上述示例中,指定了一个初始信号数量为1,最大信号量为1,名称为okcoder的信号量,接下在另外一个程序中通过Semaphore.OpenExisting方法进行获取信号量,并操作信号量的释放,如下所示:

using System;

namespace ConsoleAppWithoutTopLevelStatements2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("输入回车键,释放信号量");
            Semaphore semaphore = Semaphore.OpenExisting("okcoder");
            for (int i = 0; i < 5; i++)
            {
                if (Console.ReadKey().Key == ConsoleKey.Enter)
                {
                    semaphore.Release();
                    Console.WriteLine("释放一个信号量");
                }
            }
            Console.WriteLine("信号量已释放完成,按任意键退出");
            Console.ReadKey();
        }
    }
}

运行实例如下所示:

GIF 2025-12-12 22-21-38

在上述示例中,每输入一个Enter键,就释放一个信号量,原进程就允许进入一个资源,说明Semaphore可以被跨进程获取和操作,进而证明它是系统内核级别的同步。

SemaphoreSlim实例

SemaphoreSlim是Semaphore的一个轻量级实现,它是用户级别的,它允许在同一个进程内各线程之间进行资源的同步。SemaphoreSlim只需要指定初始线程数量,最大线程数量即可,不需要指定信号量的名称。

SemaphoreSlim主要方法有以下几个:

  • 构造方法,SemaphoreSlim提供在构造方法有2个,可以根据需要用来设置初始信号数量,最大信号数量。
  • Wait,等待方法用于等待一个信号,此方法接收一个参数用于设置超时时间,它返回一个bool值,true表示接收到信号,false表示没有接收到信号。
  • WaitAsync,支持Wait信号的异步调用。
  • Release,释放资源方法,默认释放一个资源的占用。此方法接收一个参数用于设置释放占用资源的数量。

说明:如果不需要跨进程处理资源同步的时候,SemaphoreSlim才是最佳选择。

应用示例:定义一个共享资源信号量,同一时刻允许2个线程使用,最大线程数量为5,则采用默认构造函数并指定初始值,最大值即可,如下所示:

namespace Okcoder.DemoSemaphore
{
    public class Test2
    {
        private readonly SemaphoreSlim semaphoreSlim;

        public Test2()
        {
            semaphoreSlim = new SemaphoreSlim(2, 5);
        }

        /// <summary>
        /// Work
        /// </summary>
        /// <param name="id"></param>
        public void Work(int id)
        {
            try
            {
                Console.WriteLine($"id = {id} 正在等待进入 working area.");
                semaphoreSlim.Wait();
                Console.WriteLine($"id = {id} 进入了working area.");
                //do something
                Thread.Sleep(3000);
            }
            catch
            {
                throw;
            }
            finally
            {
                Console.WriteLine($"id = {id} 离开了working area.");
                semaphoreSlim.Release();
            }
        }
    }
}

当我们创建5个线程,同时调用资源时,也只允许同一时刻2个线程使用,如下所示:

using Okcoder.DemoSemaphore;
using System;

namespace ConsoleAppWithoutTopLevelStatements
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            Test2 test = new Test2();
            for (int i = 0; i < 5; i++)
            {
                int id = i;
                Task.Run(() =>
                {
                    test.Work(id);
                });
            }
            Console.ReadKey();
        }
    }
}

运行实例,效果如下所示:

image

可以看到,每次进入共享资源的顺序都不同,但同一时刻允许进入的数量是一样的。

以上就是《推荐一种并发线程中资源同步常用方法》的全部内容,旨在抛砖引玉,一起学习,共同进步。