C#中常见的锁以及用法--18

目录

一.C#中存在的锁

二.锁的作用

三.锁的概念和定义

关于锁的完整代码示例

代码逐层剖析:

全局变量与同步变量

Lock(锁)关键字示例

Monitor(监视器锁)示例

Mutex(互斥量)示例(支持跨进程同步)

SemaphoreSlim(信号量)示例

ReadWriterLockSlim(读写锁)示例

SpinLock(自旋锁)示例

InterLocked示例

任务并行库(TPL)同步示例

总结


在C#语言中,为了在多线程环境中保护共享资源,防止线程间资源竞争,引入了多种锁的机制

本文将介绍一些常见的C#的锁以及其作用,概念和使用方式

一.C#中存在的

  1. **lock关键字(基于Monitor):**这是C#中最简单和常用的同步机制,用于确保在给定的对象上,同一时间只有一个线程可以进入被锁定的代码块
  2. **Monitor类:**提供了静态方法Enter和Exit,与lock关键字功能类似,但提供了更细粒度的控制,例如尝试进入和等待通知等
  3. **Mutex(互斥量):**系统级别的同步机制,可在跨进程之间同步进程,对比lock和Monitor,Mutex可以用于同步不同进程中的线程
  4. **Semaphore和SemaphoreSlim:**信号量允许限定一定数量的线程同时访问一个资源,SemaphoreSlim是轻量级版本,主要用于进程内同步
  5. **ReaderWriterLockSlim:**允许多个线程同时读取资源,但在写入时,要求独占访问.适用于读多写少的场景,以提高并发性能
  6. **SpinLock:**一种自旋锁,用于短时间的锁定,可以减少线程上下文切换的开销,但是需要去谨慎的去使用,避免占用CPU时间过长
  7. **Interlocked类:**提供了一组原子操作,能够在不使用锁的情况下对共享变量进行线程安全的操作,例如增减,交换,比较交换等
  8. **Task并行库中的常用机制:**如CannellationToken,Barrier,CountdownEvent等,用于更高层次的任务同步

二.锁的作用

  1. **线程同步:**确保多个线程在访问共享资源时,不会产生数据不一致或者是竞态条件
  2. **保证数据的完整性:**通过控制对资源的并发访问,防止数据损坏或者出现不可预期的结果
  3. **管理线程执行顺序:**在复杂的多线程应用中,控制线程的执行顺序,确保程序按照预期的逻辑运行

三.锁的概念和定义

  • **锁(Lock):**是一种同步机制,用于确保在同一时间,只有一个线程可以访问某一资源或者执行某段代码,锁可以防止多个线程共同修改共享数据,导致数据不一致.
  • **互斥(Mutual Exclusion):**指在多线程环境中,确保只有一个线程能够访问共享资源的特性.
  • **临界区(Critical Section):**程序中访问共享资源的代码块,需要被保护以防止并发访问.
  • **死锁(Deadlock):**当多个线程互相等待对方释放资源时,导致程序无法继续执行的状态.

关于锁的完整代码示例

cs 复制代码
    // 共享资源
    private static int _sharedResource = 0;
    private static List<int> _sharedList = new List<int>();
    
    // 各种锁的对象声明
    private static readonly object _lockObject = new object();
    private static readonly object _monitorLock = new object();
    private static readonly Mutex _mutex = new Mutex();
    private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(2); // 允许2个并发访问
    private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
    private static SpinLock _spinLock = new SpinLock();
    
    static async Task Main(string[] args)
    {
        // 1. Lock示例
        await DemonstrateLock();
        
        // 2. Monitor示例
        await DemonstrateMonitor();
        
        // 3. Mutex示例
        await DemonstrateMutex();
        
        // 4. Semaphore示例
        await DemonstrateSemaphore();
        
        // 5. ReaderWriterLock示例
        await DemonstrateReaderWriterLock();
        
        // 6. SpinLock示例
        await DemonstrateSpinLock();
        
        // 7. Interlocked示例
        DemonstrateInterlocked();
        
        // 8. 任务并行库同步示例
        await DemonstrateTPLSync();
    }

    // 1. Lock关键字示例
    private static async Task DemonstrateLock()
    {
        Console.WriteLine("\n=== Lock示例 ===");
        var tasks = new List<Task>();
        
        for (int i = 0; i < 3; i++)
        {
            tasks.Add(Task.Run(() =>
            {
                // lock块确保同一时间只有一个线程可以执行这段代码
                lock (_lockObject)
                {
                    _sharedResource++;
                    Console.WriteLine($"Lock: 线程 {Task.CurrentId} 修改资源值为: {_sharedResource}");
                    Thread.Sleep(100); // 模拟工作
                }
            }));
        }
        await Task.WhenAll(tasks);
    }

    // Monitor类示例
    private static async Task DemonstrateMonitor()
    {
        Console.WriteLine("\n=== Monitor示例 ===");
        var tasks = new List<Task>();

        for (int i = 0; i < 3; i++)
        {
            tasks.Add(Task.Run(() =>
            {
                bool lockTaken = false;
                try
                {
                    // 尝试获取锁,设置1秒超时
                    Monitor.TryEnter(_monitorLock, TimeSpan.FromSeconds(1), ref lockTaken);
                    if (lockTaken)
                    {
                        _sharedResource++;
                        Console.WriteLine($"Monitor: 线程 {Task.CurrentId} 修改资源值为: {_sharedResource}");
                        Thread.Sleep(100);
                    }
                    else
                    {
                        Console.WriteLine($"Monitor: 线程 {Task.CurrentId} 未能获取锁");
                    }
                }
                finally
                {
                    // 如果成功获取了锁,则释放
                    if (lockTaken)
                    {
                        Monitor.Exit(_monitorLock);
                    }
                }
            }));
        }
        await Task.WhenAll(tasks);
    }

    // 3. Mutex示例(支持跨进程同步)
    private static async Task DemonstrateMutex()
    {
        Console.WriteLine("\n=== Mutex示例 ===");
        var tasks = new List<Task>();
        
        // 创建一个命名互斥锁,可以跨进程使用
        using (var namedMutex = new Mutex(false, "GlobalMutexExample"))
        {
            for (int i = 0; i < 3; i++)
            {
                tasks.Add(Task.Run(() =>
                {
                    try
                    {
                        namedMutex.WaitOne();
                        _sharedResource++;
                        Console.WriteLine($"Mutex: 线程 {Task.CurrentId} 修改资源值为: {_sharedResource}");
                        Thread.Sleep(100);
                    }
                    finally
                    {
                        namedMutex.ReleaseMutex();
                    }
                }));
            }
            await Task.WhenAll(tasks);
        }
    }

    // 4. SemaphoreSlim示例
    private static async Task DemonstrateSemaphore()
    {
        Console.WriteLine("\n=== SemaphoreSlim示例 ===");
        var tasks = new List<Task>();
        
        // 创建4个任务,但同时只允许2个任务执行
        for (int i = 0; i < 4; i++)
        {
            tasks.Add(Task.Run(async () =>
            {
                await _semaphore.WaitAsync();
                try
                {
                    Console.WriteLine($"Semaphore: 线程 {Task.CurrentId} 开始执行");
                    await Task.Delay(500);
                }
                finally
                {
                    _semaphore.Release();
                    Console.WriteLine($"Semaphore: 线程 {Task.CurrentId} 释放信号量");
                }
            }));
        }
        await Task.WhenAll(tasks);
    }

    // 5. ReaderWriterLockSlim示例
    private static async Task DemonstrateReaderWriterLock()
    {
        Console.WriteLine("\n=== ReaderWriterLockSlim示例 ===");
        var tasks = new List<Task>();
        
        // 添加多个读取任务
        for (int i = 0; i < 3; i++)
        {
            tasks.Add(Task.Run(() =>
            {
                _rwLock.EnterReadLock();
                try
                {
                    Console.WriteLine($"Reader: 线程 {Task.CurrentId} 正在读取");
                    Thread.Sleep(100);
                }
                finally
                {
                    _rwLock.ExitReadLock();
                }
            }));
        }

        // 添加写入任务
        tasks.Add(Task.Run(() =>
        {
            _rwLock.EnterWriteLock();
            try
            {
                Console.WriteLine($"Writer: 线程 {Task.CurrentId} 正在写入");
                Thread.Sleep(200);
            }
            finally
            {
                _rwLock.ExitWriteLock();
            }
        }));

        await Task.WhenAll(tasks);
    }

    // 6. SpinLock示例
    private static async Task DemonstrateSpinLock()
    {
        Console.WriteLine("\n=== SpinLock示例 ===");
        var tasks = new List<Task>();
        
        for (int i = 0; i < 3; i++)
        {
            tasks.Add(Task.Run(() =>
            {
                bool lockTaken = false;
                try
                {
                    _spinLock.Enter(ref lockTaken);
                    _sharedResource++;
                    Console.WriteLine($"SpinLock: 线程 {Task.CurrentId} 修改资源值为: {_sharedResource}");
                    // SpinLock适用于非常短的操作,这里仅作演示
                }
                finally
                {
                    if (lockTaken) _spinLock.Exit();
                }
            }));
        }
        await Task.WhenAll(tasks);
    }

    // 7. Interlocked示例
    private static void DemonstrateInterlocked()
    {
        Console.WriteLine("\n=== Interlocked示例 ===");
        
        // 原子递增操作
        int value = Interlocked.Increment(ref _sharedResource);
        Console.WriteLine($"Interlocked递增后的值: {value}");

        // 原子交换操作
        int original = Interlocked.Exchange(ref _sharedResource, 100);
        Console.WriteLine($"交换前的值: {original}, 交换后的值: {_sharedResource}");

        // 比较并交换操作
        int comparand = 100;
        int newValue = 200;
        int result = Interlocked.CompareExchange(ref _sharedResource, newValue, comparand);
        Console.WriteLine($"比较交换结果: {result}, 当前值: {_sharedResource}");
    }

    // 8. 任务并行库同步示例
    private static async Task DemonstrateTPLSync()
    {
        Console.WriteLine("\n=== TPL同步示例 ===");
        
        // 使用CancellationTokenSource来控制任务取消
        using (var cts = new CancellationTokenSource())
        {
            // 创建一个Barrier,等待3个任务都完成某个阶段
            var barrier = new Barrier(3);
            var tasks = new List<Task>();

            for (int i = 0; i < 3; i++)
            {
                tasks.Add(Task.Run(() =>
                {
                    Console.WriteLine($"任务 {Task.CurrentId} 开始第一阶段");
                    barrier.SignalAndWait(); // 等待所有任务完成第一阶段

                    Console.WriteLine($"任务 {Task.CurrentId} 开始第二阶段");
                    barrier.SignalAndWait(); // 等待所有任务完成第二阶段

                }, cts.Token));
            }

            await Task.WhenAll(tasks);
        }
Console.ReadKey();
    }

代码逐层剖析:

全局变量与同步变量

cs 复制代码
// 共享资源   
private static int _sharedResource = 0;   
private static List<int> _sharedList = new List<int>();
   
// 各种锁的对象声明   
private static readonly object _lockObject = new object();   
private static readonly object _monitorLock = new object();   
private static readonly Mutex _mutex = new Mutex();   
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(2); // 允许2个并发访问   
private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();   
private static SpinLock _spinLock = new SpinLock();

_sharedResource:这是一个整数,作为共享的资源,多个线程会对其进行读写

_sharedList:一个整数列表,用于示例需要

_lockObject等同步对象:这些对象用于各自的同步原语,以确保线程安全

Lock(锁)关键字示例

cs 复制代码
 private static async Task DemonstrateLock()
 {
     Console.WriteLine("\n=== Lock示例 ===");
     var tasks = new List<Task>();
     
     for (int i = 0; i < 3; i++)
     {
         tasks.Add(Task.Run(() =>
         {
             // lock块确保同一时间只有一个线程可以执行这段代码
             lock (_lockObject)
             {
                 _sharedResource++;
                 Console.WriteLine($"Lock: 线程 {Task.CurrentId} 修改资源值为: {_sharedResource}");
                 Thread.Sleep(100); // 模拟工作
                 Console.WriteLine($"Lock: 线程 {Task.CurrentId} 释放锁");
             }
         }));
     }
     await Task.WhenAll(tasks);
 }

代码解释:

  • **目的:**演示了lock关键字的使用,即如何确保在一个代码块内同时只能有一个线程运行
  • 实现细节:
    • 创建了三个任务,每个任务都尝试访问共享资源 _sharedResource
    • 使用lock(_lockObject)来锁定代码块,这样同一时间只有一个线程能够进入该代码块
    • 在锁定的代码块内,递增_sharedResource的值并输出当前线程的信息
  • **重要性:**lock是C#中最基本的同步机制,使用简单,但只能用于同一进程内的线程同步

代码运行结果展示

Monitor(监视器锁)示例

cs 复制代码
private static async Task DemonstrateMonitor()   
{
    Console.WriteLine("\n=== Monitor示例 ===");
    var tasks = new List<Task>();

    for (int i = 0; i < 3; i++)
    {
        tasks.Add(Task.Run(() =>
        {
            bool lockTaken = false;
            try
            {
                // 尝试获取锁,设置1秒超时
                Monitor.TryEnter(_monitorLock, TimeSpan.FromSeconds(1), ref lockTaken);
                if (lockTaken)
                {
                    _sharedResource++;
                    Console.WriteLine($"Monitor: 线程 {Task.CurrentId} 修改资源值为: {_sharedResource}");
                    Thread.Sleep(100);
                }
                else
                {
                    Console.WriteLine($"Monitor: 线程 {Task.CurrentId} 未能获取锁");
                }
            }
            finally
            {
                // 如果成功获取了锁,则释放
                if (lockTaken)
                {
                    Console.WriteLine($"Monitor: 线程 {Task.CurrentId} 释放锁");
                    Monitor.Exit(_monitorLock);
                }
            }
        }));
    }
    await Task.WhenAll(tasks);
}

代码解释:

  • **目的:**演示Monitor类的使用,特别是TryEnter方法,以及如何手动管理锁的获取和释放
  • 实现细节:
    • 创建了三个任务,每个任务尝试获取_monitorLock的锁
    • 使用Monitor.TryEnter方法,设置了一秒的超时时间,如果在这段时间内未能获取到锁,则返回False
    • lockTaken是一个布尔量,表示是否成功获取到锁
    • 如果成功获取了锁,执行共享资源的操作;否则输出未能获取锁的信息
    • 在Finally块中,检查是否获取了锁,如果是,则调用Monitor.Exit释放锁
  • **重要性:**Monitor类提供了比lock更灵活的机制,比如超时等待,尝试获取锁等

代码运行结果展示:

Mutex(互斥量)示例(支持跨进程同步)

cs 复制代码
private static async Task DemonstrateMutex()
{
    Console.WriteLine("\n=== Mutex示例 ===");
    var tasks = new List<Task>();
    
    // 创建一个命名互斥锁,可以跨进程使用
    using (var namedMutex = new Mutex(false, "GlobalMutexExample"))
    {
        for (int i = 0; i < 3; i++)
        {
            tasks.Add(Task.Run(() =>
            {
                try
                {
                    namedMutex.WaitOne();
                    _sharedResource++;
                    Console.WriteLine($"Mutex: 线程 {Task.CurrentId} 修改资源值为: {_sharedResource}");
                    Thread.Sleep(100);
                }
                finally
                {
                    Console.WriteLine($"Mutex: 线程 {Task.CurrentId} 释放锁");
                    namedMutex.ReleaseMutex();
                }
            }));
        }
        await Task.WhenAll(tasks);
    }
}

代码解释:

  • **目的:**演示Mutex(互斥量)的使用,特别是命名互斥量的跨进程同步特性
  • 实现细节:
    • 使用 new Mutex(false,"GlobalMutexExample")创建了一个命名的全局互斥锁,可以在不同的进程间共享
    • 创建了3个任务,每个任务尝试进入互斥锁的临界区
    • 使用 namedMutex.WaitOne()等待获取互斥锁
    • 获取锁后,修改共享资源并输出信息
    • 在finally块中,调用namedMutex.ReleaseMutex()释放互斥锁
  • 重要性:Mutex可以用于跨进程的同步,这在需要多个进程访问同一资源时十分有用

代码运行结果展示:

SemaphoreSlim(信号量)示例

cs 复制代码
private static async Task DemonstrateSemaphore()   
{
    Console.WriteLine("\n=== SemaphoreSlim示例 ===");
    var tasks = new List<Task>();
    
    // 创建4个任务,但同时只允许2个任务执行
    for (int i = 0; i < 4; i++)
    {
        tasks.Add(Task.Run(async () =>
        {
            await _semaphore.WaitAsync();
            try
            {
                Console.WriteLine($"Semaphore: 线程 {Task.CurrentId} 开始执行");
                await Task.Delay(500);
            }
            finally
            {
                _semaphore.Release();
                Console.WriteLine($"Semaphore: 线程 {Task.CurrentId} 释放信号量");
            }
        }));
    }
    await Task.WhenAll(tasks);
}

代码解释:

  • **目的:**演示SemaphoreSlim的使用,控制同时访问某资源的线程数
  • 实现细节:
    • _semaphore初始化时设置了初始计数值为2,因此最多允许2个线程同时进入
    • 创建了四个任务,每个任务都试图获取信号量
    • 使用await _semaphore.WaitAsync() 异步地等待信号量
    • 获取信号量后,输出信息并等待500毫秒,模拟工作
    • 在finally块中,调用 _semaphore.Release() 方法去释放信号量,并输出信息
  • **重要性:**信号量用于限制同时访问某资源或代码块的线程数量,SemaphoreSlim是线程内的轻量级版本

代码运行展示:

ReadWriterLockSlim(读写锁)示例

cs 复制代码
private static async Task DemonstrateReaderWriterLock()   
{
    Console.WriteLine("\n=== ReaderWriterLockSlim示例 ===");
    var tasks = new List<Task>();
    
    // 添加多个读取任务
    for (int i = 0; i < 3; i++)
    {
        tasks.Add(Task.Run(() =>
        {
            _rwLock.EnterReadLock();
            try
            {
                Console.WriteLine($"Reader: 线程 {Task.CurrentId} 正在读取");
                Thread.Sleep(100);
            }
            finally
            {
                _rwLock.ExitReadLock();
            }
        }));
    }

    // 添加写入任务
    tasks.Add(Task.Run(() =>
    {
        _rwLock.EnterWriteLock();
        try
        {
            Console.WriteLine($"Writer: 线程 {Task.CurrentId} 正在写入");
            Thread.Sleep(200);
        }
        finally
        {
            _rwLock.ExitWriteLock();
        }
    }));

    await Task.WhenAll(tasks);
}

代码解释:

  • **目的:**演示ReadWriterLockSlim的使用,允许多个线程同时读取,但写入时需要独占
  • 实现细节:
    • 创建了3个读取任务,使用_rwLock.EnterReadLock()获取读锁
    • 创建了1个写入任务,使用_rwLock.EnterWriteLock()获取写锁
    • 读取任务之间可以并发执行,但写入任务需要等待所有读锁释放后才能获取写锁
    • 每个任务在完成后,调用相应的ExitReadLock()和ExitWriteLock()释放锁
  • **重要性:**读写锁提高了并发性能,适用于读多写少的情景

代码运行结果展示:

SpinLock(自旋锁)示例

cs 复制代码
private static async Task DemonstrateSpinLock()   
{
    Console.WriteLine("\n=== SpinLock示例 ===");
    var tasks = new List<Task>();
    
    for (int i = 0; i < 3; i++)
    {
        tasks.Add(Task.Run(() =>
        {
            bool lockTaken = false;
            try
            {
                _spinLock.Enter(ref lockTaken);
                _sharedResource++;
                Console.WriteLine($"SpinLock: 线程 {Task.CurrentId} 修改资源值为: {_sharedResource}");
                // SpinLock适用于非常短的操作,这里仅作演示
            }
            finally
            {
                if (lockTaken) _spinLock.Exit();
            }
        }));
    }
    await Task.WhenAll(tasks);
}

代码解释:

  • **目的:**演示了SpinLock的使用,它是一种自旋锁,适用于非常短时间的锁定操作
  • 实现细节 :
    • 创建了三个任务,每个任务都尝试获取_spinLock的锁
    • 使用_spinLock.Enter(ref lockTaken)来获取锁
    • 获取锁后,修改共享资源并输出信息
    • 在finally块中,检查lockTaken,如果获取了锁,则调用_spinLock.Exit()方法释放锁
  • **重要性:**SpinLock适用于非常短的临界区,因为它会一直循环等待直至获取锁,避免线程上下文的切换,但如果操作时间过长,会导致CPU占用过高

在使用自旋锁时,一定要去释放锁,否则会造成死锁

代码运行结果展示:

InterLocked示例

cs 复制代码
private static void DemonstrateInterlocked()   
{
    Console.WriteLine("\n=== Interlocked示例 ===");
    
    // 原子递增操作
    int value = Interlocked.Increment(ref _sharedResource);
    Console.WriteLine($"Interlocked递增后的值: {value}");

    // 原子交换操作
    int original = Interlocked.Exchange(ref _sharedResource, 100);
    Console.WriteLine($"交换前的值: {original}, 交换后的值: {_sharedResource}");

    // 比较并交换操作
    int comparand = 100;
    int newValue = 200;
    int result = Interlocked.CompareExchange(ref _sharedResource, newValue, comparand);
    Console.WriteLine($"比较交换结果: {result}, 当前值: {_sharedResource}");
}

代码解释:

  • **目的:**演示InterLocked类的使用,用于执行原子操作,避免显示的锁定
  • 实现细节:
    • Interlocked.Increment:对共享变量执行原子递增操作,返回递增后的值
    • Interlocked.Exchange:将共享变量设置为新值,并返回原始值
    • Interlocked.CompareExchange:如果共享变量的值等于比较值,则将其设置为新值,并返回原始值
  • 重要性:InterLocked提供了高效的线程安全操作,适用于简单的变量操作,而不需要使用锁

代码运行结果展示:

任务并行库(TPL)同步示例

cs 复制代码
private static async Task DemonstrateTPLSync()   
{
    Console.WriteLine("\n=== TPL同步示例 ===");
    
    // 使用CancellationTokenSource来控制任务取消
    using (var cts = new CancellationTokenSource())
    {
        // 创建一个Barrier,等待3个任务都完成某个阶段
        var barrier = new Barrier(3);
        var tasks = new List<Task>();

        for (int i = 0; i < 3; i++)
        {
            tasks.Add(Task.Run(() =>
            {
                Console.WriteLine($"任务 {Task.CurrentId} 开始第一阶段");
                barrier.SignalAndWait(); // 等待所有任务完成第一阶段

                Console.WriteLine($"任务 {Task.CurrentId} 开始第二阶段");
                barrier.SignalAndWait(); // 等待所有任务完成第二阶段

            }, cts.Token));
        }

        await Task.WhenAll(tasks);
    }
    Console.ReadKey();
}

代码解释:

  • 目的:演示任务并行库(TPL)中的同步机制,特别是Barrier的使用
  • 实现细节:
    • 创建了一个Barrier,参与方数量为3,表示需要等待三个任务
    • 创建了3个任务,每个任务在两个阶段都调用了barrier.SignalAndWait();
      • 第一阶段:任务输出开始第一阶段的信息,然后调用barrier.SignalAndWait(),等待其他任务也完成第一阶段
      • 第二阶段:任务输出开始第二阶段的信息,然后再次调用barrier.SignalAndWait()
    • CancellationTokenSource可以控制任务的取消
  • 重要性:Barrier用于协调多个任务的执行,使得它们可以在某个同步点等待彼此,同步进入下一阶段的操作

代码运行结果展示:


总结

  • lock和Monitor:适用于简单的线程同步,同一进程内的线程
  • Mutex:可用于跨进程的同步,但性能较 lock 较低
  • SemaphoreSlim:控制同时访问资源的线程数量
  • ReadWriterLockSlim:优化读多写少的场景,提高并发性能
  • SpinLock:适用于非常短的锁定操作,避免线程上下文切换,但可能导致CPU占用高
  • InterLocked:提供简单的原子操作,性能高,没有锁开销
  • 任务并行库同步机制""提供高级的同步方式,如 Barrier,CountdownEvent
相关推荐
顽疲几秒前
java 小红书源码 1:1还原 uniapp
java·开发语言·uni-app
Mae_cpski8 分钟前
【React学习笔记】第三章:React应用
笔记·学习·react.js
程序员 小柴11 分钟前
会话_JSP_过滤器_监听器_Ajax
java·开发语言·ajax
Yoyo25年秋招冲冲冲12 分钟前
【Java回顾】Day7 Java IO|分类(传输方式,数据操作)|零拷贝和NIO
java·开发语言·nio
Icoolkj26 分钟前
微服务学习-快速搭建
学习·微服务·架构
柯南二号35 分钟前
【Kotlin】上手学习之类型篇
开发语言·学习·kotlin
电脑玩家粉色男孩40 分钟前
二、学习SpringMVC
java·学习·servlet
网络空间站43 分钟前
T-SQL语言的学习路线
开发语言·后端·golang
张声录11 小时前
【Gossip 协议】Golang的实现库Memberlist 库简介
开发语言·后端·golang·运维开发·prometheus·devops
自学AI的鲨鱼儿1 小时前
Mac 使用 GVM 管理多版本 Go 环境
开发语言·macos·golang