博客 (821)

  1. 临界区与 lock 关键字

    核心作用:

    通过将多线程访问串行化,保护共享资源或代码段。lock 关键字是 Monitor 类的语法糖,提供异常安全的临界区实现。

    实现示例:

    // 创建私有静态只读对象
    // private static readonly object _lockObj = new object();
    private static readonly System.Threading.Lock _locker = new(); // .NET 9+ 推荐使用 Lock 类型,避免传统 object 的性能损耗
    
    public void ThreadSafeMethod()
    {
        lock (_lockObj) 
        {
            // 临界区代码(每次仅一个线程可进入)
        }
    }

    超时机制:

    高并发场景可结合 Monitor.TryEnter 设置超时,避免无限等待:


    if (Monitor.TryEnter(lockObject, TimeSpan.FromSeconds(1)))
    {
        try { /* 操作 */ }
        finally { Monitor.Exit(lockObject); }
    }

    关键特性:

    用户态锁(无内核切换开销)

    自动调用Monitor.Enter和Monitor.Exit

    必须使用专用私有对象作为锁标识

    注意事项:

    ❌ 避免锁定this、Type实例或字符串(易引发死锁)

    ❌ 避免嵌套锁(需严格按顺序释放)

    ✅ 推荐readonly修饰锁对象

    ❌ lock 不适用于 async/await,需使用 SemaphoreSlim 实现异步锁

    lock 示例

     

  2. 互斥锁(Mutex)

    核心作用:

    系统级内核锁,支持跨进程同步,但性能开销较高(用户态/内核态切换)。

    实现示例:

    using var mutex = new Mutex(false, "Global\\MyAppMutex");
    
    try 
    {
        // 等待锁(最大等待时间500ms)
        if (mutex.WaitOne(500)) 
        {
            // 临界区代码
        }
    }
    finally 
    {
        if (mutex != null)
        {
            mutex.ReleaseMutex();
        }
    }

    关键特性:

    支持跨应用程序域同步

    线程终止时自动释放锁

    支持命名互斥体(系统全局可见)

    适用场景:

    单实例应用程序控制

    进程间共享文件访问

    硬件设备独占访问

    Mutex 示例

     

  3. 信号量(Semaphore)

    核心作用:

    通过许可计数器控制并发线程数,SemaphoreSlim为轻量级版本(用户态实现)。

    实现对比:

    类型
    跨进程
    性能
    最大许可数
    Semaphore
    ✔️

    系统限制
    SemaphoreSlim


    Int32.Max

    代码示例:

    // 创建初始3许可、最大5许可的信号量
    var semaphore = new SemaphoreSlim(3, 5);
    
    semaphore.Wait();  // 获取许可
    try 
    {
        // 资源访问代码
    }
    finally 
    {
        semaphore.Release();
    }

    异步编程

    private readonly SemaphoreSlim _asyncLock = new(1, 1);
    public async Task UpdateAsync()
    {
        await _asyncLock.WaitAsync();
        try { /* 异步操作 */ }
        finally { _asyncLock.Release(); }
    }

    典型应用:

    数据库连接池(限制最大连接数)

    API 请求限流

    批量任务并发控制

    Semaphore 示例

     

  4. 事件(Event)

    核心机制:

    通过信号机制实现线程间通知,分为两种类型:

    类型
    信号重置方式
    唤醒线程数
    AutoResetEvent
    自动
    单个
    ManualResetEvent
    手动
    所有

    使用示例:

    var autoEvent = new AutoResetEvent(false);
    
    // 等待线程
    Task.Run(() => 
    {
        autoEvent.WaitOne();
        // 收到信号后执行
    });
    
    // 信号发送线程
    autoEvent.Set();  // 唤醒一个等待线程

    高级用法:

    配合WaitHandle.WaitAll实现多事件等待

    使用ManualResetEventSlim提升性能

    AutoResetEvent 示例

     

  5. 读写锁(ReaderWriterLockSlim)

    核心优势:

    实现读写分离的并发策略,适合读多写少场景(如缓存系统)。

    锁模式对比:

    模式
    并发性
    升级支持
    读模式(EnterReadLock)
    多线程并发读

    写模式(EnterWriteLock)
    独占访问

    可升级模式
    单线程读→写
    ✔️

    代码示例:

    var rwLock = new ReaderWriterLockSlim();
    
    // 读操作
    rwLock.EnterReadLock();
    try 
    {
        // 只读访问
    }
    finally 
    {
        rwLock.ExitReadLock();
    }
    
    // 写操作
    rwLock.EnterWriteLock();
    try 
    {
        // 排他写入
    }
    finally 
    {
        rwLock.ExitWriteLock();
    }

    最佳实践:

    优先使用ReaderWriterLockSlim(旧版有死锁风险)

    避免长时间持有读锁(可能饿死写线程)

    ReaderWriterLockSlim 示例

  6. 原子操作(Interlocked)

    原理:

    通过CPU指令实现无锁线程安全操作。

    常用方法:

    int counter = 0;
    Interlocked.Increment(ref counter);      // 原子递增
    Interlocked.Decrement(ref counter);      // 原子递减
    Interlocked.CompareExchange(ref value, newVal, oldVal);  // CAS操作

    适用场景:

    简单计数器

    标志位状态切换

    无锁数据结构实现

     

  7. 自旋锁(SpinLock)

    核心特点:

    通过忙等待(busy-wait)避免上下文切换,适用极短临界区(<1微秒)。

    实现示例:

    private SpinLock _spinLock = new SpinLock();
    
    public void CriticalOperation()
    {
        bool lockTaken = false;
        try 
        {
            _spinLock.Enter(ref lockTaken);
            // 极短临界区代码
        }
        finally 
        {
            if (lockTaken) _spinLock.Exit();
        }
    }

    优化技巧:

    单核CPU需调用Thread.SpinWait或Thread.Yield

    配合SpinWait结构实现自适应等待

     

  8. 同步机制对比指南

    机制
    跨进程
    开销级别
    最佳适用场景
    lock


    通用临界区保护
    Mutex
    ✔️

    进程间资源独占
    Semaphore
    ✔️

    并发数限制(跨进程)
    SemaphoreSlim


    并发数限制(进程内)
    ReaderWriterLockSlim


    读多写少场景
    SpinLock

    极低
    纳秒级临界区
    Interlocked
    -无锁
    简单原子操作

选择原则:

  1. 优先考虑用户态锁(lock/SpinLock/SemaphoreSlim)

  2. 跨进程需求必须使用内核对象(Mutex/Semaphore)

  3. 读写比例超过10:1时考虑读写锁

  4. 自旋锁仅用于高频短操作(如链表指针修改)

通过以上结构化的分类和对比,开发者可以更精准地选择适合特定场景的线程同步方案。建议在实际使用中配合性能分析工具(如BenchmarkDotNet)进行量化验证。


💡 ASP.NET 的异步编程(async/await)本质是单进程内的线程调度,不算“跨进程”。每个 IIS 应用程序池对应一个独立的工作进程(w3wp.exe),不同用户访问同一应用程序池下的 ASP.NET 网站,两者的请求均由同一个 w3wp.exe 进程处理。可能跨进程的场景有:Web Garden 配置、多应用程序池部署等。

xoyozo 3 天前
77
字段
MySQL 中的类型和精度
MySQL 中占用字节原因说明
股票价格DECIMAL(10,2)5

股票价格四舍五入保留两位小数,避免浮点型精度丢失。

即使最贵的股票价格并没有超过 9999.99 元,但考虑到复权价和扩展性,建议用更大的类型。

基金价格

基金价格四舍五入保留三位小数,避免浮点型精度丢失。
精确的成交金额(元)、成交量(手)
BIGINT UNSIGNED8

取值范围 0 到 1844,6744,0737,0955,1615(即一千多个亿亿)。

2024年A股总成交金额为257万亿元。

不精确的成交金额、成交量float4取值范围足够大,有效数字约为6-7位。
交易日DATE3
包括年、月、日
交易时间DATETIME8精确到分钟
6位股票代码CHAR(6)66个半角字符占6个字节
股票名称
VARCHAR(10)
utf-8 的一个汉字占3个字节


xoyozo 9 天前
72

在 .NET Framework 的缓存管理中,cacheMemoryLimitMegabytes 是一个关键配置属性,用于控制内存缓存(MemoryCache)实例的最大内存占用。以下是其具体用法及实现细节:

  1. 基本定义与作用

    • 功能:通过 cacheMemoryLimitMegabytes 可设置 MemoryCache 实例允许占用的最大内存(单位:MB)。若缓存数据超过此限制,系统会自动淘汰旧条目。

    • 默认值:默认值为 0,表示缓存基于计算机的物理内存自动管理(例如根据可用内存动态调整)。

  2. 配置方式

    • 通过配置文件(web.config)

      在 web.config 的 <system.runtime.caching> 节点下配置 namedCaches,示例:

      <configuration>
        <system.runtime.caching>
          <memoryCache>
            <namedCaches>
              <add 
                name="Default" 
                cacheMemoryLimitMegabytes="500" 
                physicalMemoryLimitPercentage="50" 
                pollingInterval="00:05:00" />
            </namedCaches>
          </memoryCache>
        </system.runtime.caching>
      </configuration>

      参数说明:

      • cacheMemoryLimitMegabytes:最大内存限制(例如 500 表示 500MB)。

      • physicalMemoryLimitPercentage:允许使用的物理内存百分比(可选)。

      • pollingInterval:缓存清理策略的轮询间隔(例如每5分钟检查一次)。

    • 通过代码动态配置

      在初始化 MemoryCache 时,通过 NameValueCollection 传递参数:

      var config = new NameValueCollection
      {
          { "cacheMemoryLimitMegabytes", "500" },
          { "physicalMemoryLimitPercentage", "50" },
          { "pollingInterval", "00:05:00" }
      };
      var cache = new MemoryCache("CustomCache", config);

      此方式适用于需要动态调整缓存策略的场景。

  3. 注意事项

    • 优先级规则:

      若同时配置了 cacheMemoryLimitMegabytes 和 physicalMemoryLimitPercentage,系统会选择两者中较小的值作为限制。

    • 分布式缓存兼容性:

      此属性仅适用于进程内缓存(如 MemoryCache),若使用 Redis 等分布式缓存需通过其独立配置管理内存。

    • 监控与调试:

      建议结合性能计数器(如 ASP.NET Applications 类别下的 Cache Total Entries)或日志记录模块(参考 web.config 的 <system.diagnostics> 配置)监控实际内存占用。

  4. 应用场景示例

    场景:一个电商网站需要缓存商品目录数据,限制最大内存为 1GB。

    配置实现:

    <add 
      name="ProductCatalogCache" 
      cacheMemoryLimitMegabytes="1024" 
      pollingInterval="00:10:00" />

    代码调用:

    var productCatalog = MemoryCache.Default["ProductCatalog"];
  5. 常见问题

    1. Q:设置为 0 时缓存会无限制增长吗?

      A:不会。此时缓存基于系统物理内存动态管理,通常上限为总内存的 70%-90%。

    2. Q:如何验证配置已生效?

      A:可通过 MemoryCache.GetCount() 统计条目数量,或使用性能监视器跟踪内存占用。

xoyozo 13 天前
125
方法/工具发布时间所属框架命名空间/依赖项
编码标准空格处理严格性适用场景现代项目支持(.NET 6+)
HttpUtility.UrlEncode2002.NET Framework 1.0+System.Web(需引用 DLL)x-www-form-urlencoded+宽松传统 ASP.NET WebForms
Server.UrlEncode2002.NET Framework 1.0+System.Web(ASP.NET 页面内)x-www-form-urlencoded+宽松ASP.NET WebForms 页面内编码
Uri.EscapeDataString2005.NET Framework 2.0+System(核心库)RFC 3986%20严格构造 URI 组件(路径、查询参数)✔️
WebUtility.UrlEncode2012.NET Framework 4.5+System.Net(跨平台)x-www-form-urlencoded+宽松非 Web 环境或兼容旧代码✔️
UrlEncoder.Default.Encode2016.NET Core 1.0+System.Text.Encodings.WebRFC 3986%20严格现代应用,严格 URI 编码✔️

关键选择原则

  • 兼容旧代码 → HttpUtility.UrlEncode 或 WebUtility.UrlEncode。

  • 严格 URI 规范 → Uri.EscapeDataString 或 UrlEncoder。

  • ASP.NET Core → UrlEncoder。

  • 非 Web 或跨平台 → 弃用 System.Web,选择 System.Net 或 System.Text.Encodings.Web 中的方法。


xoyozo 21 天前
188
zoom显示
1洲名
2洲名
3国名
4国名、首都
5省名
6省名、省会
7地级市
8地级市名、县级市名
9地级市名、县级市名、部分街道/镇名
10
地级市名、县级市名、部分街道/镇名
11
地级市名、县级市名、部分街道/镇名、部分村名
12
地级市名、县级市名、部分街道/镇名、部分村名
13
地标
14
村名
15
小区名
16
建筑物轮廓
17
建筑物幢号

设置缩放级别:

map.centerAndZoom(new T.LngLat(108.95, 34.27), 4); // 默认显示整个中国地图、省级边界线
map.setMinZoom(2); // 世界地图
map.setMaxZoom(8); // 区/县/市


xoyozo 21 天前
135
  1. 打开“任务计划程序”(taskschd.msc)

    image.png

  2. 点击右侧“创建任务”

    image.png

  3. 填写“名称”

    image.png

  4. “安全选项”根据实际情况设置

    如果选择“不管用户是否登录都要运行”,则启动成功后不会显示窗口(任务管理器中可见进程)

    如果选择“只在用户登录时运行”启动成功后会显示窗口,但系统重启后需要进入系统才能运行此计划

  5. “触发器”新建,勾选“重复任务间隔”选最短,“持续时间”无限期,并取消“任务的执行时间超过此值则停止执行”

    image.png

  6. “操作”新建,启动程序,浏览程序或脚本

    image.png

  7. “设置”请勿启动新实例(只判断它启动的实例,不判断手动打开的或开机启动的实例),其它选项按需设置

    image.png

  8. 设置完成

    image.png


xoyozo 1 个月前
309

打开 .csproj 项目文件,在 <PropertyGroup> 标签内添加:

<Version>
  1.0.$([System.Math]::Floor($([System.DateTime]::Now.Subtract($([System.DateTime]::Parse('2000-01-01T00:00:00Z'))).TotalDays))).$([MSBuild]::Divide($([System.Math]::Floor($([System.DateTime]::Now.TimeOfDay.TotalSeconds))), 2))
</Version>

最终生成的版本号示例: 1.0.9238.28518

其中,Major 与 Minor 是固定的,Build 是2000年1月1日至今的天数,Revision 是今天的秒数 / 2 所得的值。(为了防止数值超过 65535)


程序中获取版本号:

var version = Assembly.GetExecutingAssembly().GetName().Version!; // 当前类库
var version = Assembly.GetEntryAssembly()?.GetName().Version!; // 入口项目


从版本号获取发布时间:

DateTime versionTime = new DateTime(2000, 1, 1).AddDays(version.Build).AddSeconds(version.Revision * 2);


查看 .NET Framework 实现自动版本号的方法


xoyozo 1 个月前
290
  1. 在解决方案资源管理器中找到 Properties/AssemblyInfo.cs 文件。该文件存放程序集版本信息。

  2. 修改版本号格式

    将以下代码片段中的 AssemblyVersion 改为使用星号通配符(建议保留主版本和次版本号):

    [assembly: AssemblyVersion("1.0.*")]  // 自动生成构建号和修订号
    // [assembly: AssemblyFileVersion("1.0.0.0")] // 注释或删除此行
  3. 关闭确定性构建

    用文本编辑器打开 .csproj 项目文件,在 <PropertyGroup> 标签内添加:

    <Deterministic>false</Deterministic>

    此设置允许 MSBuild 生成动态版本号。

最终生成的版本号示例: 1.0.9238.28518

其中,Major 与 Minor 是固定的,Build 是2000年1月1日至今的天数,Revision 是今天的秒数 / 2 所得的值。(为了防止数值超过 65535)


程序中获取版本号:

var version = Assembly.GetExecutingAssembly().GetName().Version;


从版本号获取发布时间:

DateTime versionTime = new DateTime(2000, 1, 1).AddDays(version.Build).AddSeconds(version.Revision * 2);


查看 .NET Core / .NET 5+ 实现自动版本号的方法


xoyozo 1 个月前
293

通常,我们在更新一条数据库记录时,EF 先取出这一条记录:

var log = DbContext.MyTable.Find(id);

然后赋值字段并保存:

log.Result = "OK";
DbContext.SaveChanges();

这样就会产生两次数据库查询。


我们尝试用 Attach 方法。

先创建一个仅包含主键的对象:

var log  = new MyTable { Id = id };

将对象附加到上下文:

DbContext.MyTable.Attach(log);

然后更新需要更新的字段:

log.Result = "OK";
DbContext.SaveChanges();

这样,能在保留其它字段值的前提下,减少一次数据库查询。

但是需要注意的是:

当某非 null 字段需要恢复默认值时,EF 会忽略这个更改。(可能会因 EF 版本等原因有不同的结论)

举个例子:

某记录有个 int 型字段 a,在数据库中这个记录的 a 的值为 1,但 C# 中 int 型的默认值为 0,所以当 Attach 附加这个对象后,如果重新设置 log.a 为 0,那么保存后 a 的值仍为 1。


还有一种写法,利用 ExecuteUpdate 方法:

var affectedRows = DbContext.MyTable
  .Where(c => c.Id == id)
  .ExecuteUpdate(setters => setters
    .SetProperty(c => c.Result, "OK")
  );

返回匹配的行数。

这种写法不会遇到“恢复为默认值不生效”的问题,推荐使用。

xoyozo 1 个月前
304
数据库数据类型收费情况调用方式数据来源资料
Tushare Pro股票、指数、公募、期货、期权、债券、外汇、港股、美股……免费 / 收费Web API / Python / Matlab / R通过社区的采集和整理存入数据库经过质量控制后再提供给用户积分 / 文档
麦蕊智数沪深股票基础数据、实时交易数据、指数数据免费 / 收费Web API
licence / 文档
ig507沪深股票基础数据、实时交易数据、指数数据、基金数据免费 / 收费Web API
licence / 文档
AkShare股票、期货、债券、期权、外汇、货币、指数……免费开源Python / Anaconda / R / MATLAB / 本地 Web API基于爬虫技术从大型财经网站抓取公开数据文档 / AKTools
BaoStock仅提供历史数据(如日线、周线),‌无实时行情免费开源

本地调用 Python API


数据来源为交易所或合作机构的标准化接口‌文档


xoyozo 1 个月前
344