欢迎访问yhm138的博客园博客, 你可以通过 [RSS] 的方式持续关注博客更新

MyAvatar

yhm138

HelloWorld!

C#中有哪些线程同步的办法

这里着重关注如何做到线程间的同步,即:有些资源不能同时让多个线程同时访问/操作否则会引起麻烦;线程在某些时机需要等待其他线程才会进行下一步

除了lock和Semaphore之外,C# 还有其他的线程同步方法,如 Monitor, Mutex, ReaderWriterLockSlim 和 ManualResetEvent等。

在常见的编程语言中,同步原语可以分类为哪些?

常见的编程语言中,同步原语主要包括以下几类:

锁(Locks):锁是一种最基本的同步原语,用于限制对共享资源的访问。当一个线程获取锁时,其他线程必须等待该线程释放锁后才能访问共享资源。锁分为不可重入锁和可重入锁(Reentrant Locks)。

信号量(Semaphores):信号量是一种更通用的同步原语,主要用于限制对共享资源的访问次数。信号量维护一个计数器,表示可用资源的数量。线程通过获取和释放信号量来实现同步。

互斥量(Mutexes):互斥量(互斥锁)是一种特殊的锁,用于确保同一时间只有一个线程可以访问某个资源。互斥量可以看作是信号量的特例,计数器值为1。

条件变量(Condition Variables):条件变量用于线程间的同步和通信。一个线程可以等待某个条件成立,而另一个线程可以通知该条件已成立。通常与互斥量配合使用,以实现对共享资源的安全访问。

屏障(Barriers):屏障用于同步一组线程,确保所有线程都到达某个同步点后才能继续执行。这在需要多个线程分阶段完成任务时非常有用。 思想类似LoadRunner里的集合点

这些同步原语在不同编程语言和操作系统中有不同的实现方式和命名,但它们的基本概念和功能是相似的。

关于锁的名词解释

放弃名词解释——看这篇文章了解锁的分类 https://juejin.cn/post/7010305230256488485

锁可重入(Reentrant)是指一个线程可以多次获得同一个锁,而不会产生死锁。当一个线程已经拥有了一个锁,再次尝试获得相同的锁时,该线程可以继续执行,而不会阻塞等待锁释放。

自旋锁(SpinLock)是一种特殊类型的锁,当一个线程试图获得已被其他线程持有的锁时,不会立即进入阻塞状态(和自旋锁相对应的是阻塞锁(获取锁失败后会阻塞))。相反,该线程会在一个循环中忙等待(busy-waiting),不断地检查锁是否可用。自旋锁是一种低级别的同步原语,通常在需要极低延迟和高性能的场景下使用。

C#里有哪些线程同步的办法

lock

优点:简单易用,适用于大多数场景,可重入。
缺点:无法手动释放锁,无法跨进程使用。

TIO

using System;
using System.Threading;

class Example
{
    private static object syncObj = new object();
    private static int counter = 0;

    static void Main()
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Counter: " + counter);
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            lock (syncObj)
            {
                counter++;
		Console.WriteLine("Counter: " + counter);
            }
        }
    }
}

Semaphore

优点:可以控制同时访问资源的线程数量,可跨进程使用。
缺点:相对于lock,使用起来稍复杂。

using System;
using System.Threading;

class Example
{
    private static Semaphore sem = new Semaphore(1, 1); // Initial count: 1, Maximum count: 1
    private static int counter = 0;

    static void Main()
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Counter: " + counter);
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            sem.WaitOne();
            counter++;
            sem.Release();
        }
    }
}

Monitor

优点:灵活性高,可手动控制锁的获取和释放,可重入。
缺点:使用起来较复杂,容易出错,无法跨进程使用。

using System;
using System.Threading;

class Example
{
    private static object syncObj = new object();
    private static int counter = 0;

    static void Main()
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Counter: " + counter);
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            bool lockTaken = false;
            try
            {
                Monitor.Enter(syncObj, ref lockTaken);
                counter++;
            }
            finally
            {
                if (lockTaken)
                {
                    Monitor.Exit(syncObj);
                }
            }
        }
    }
}

Mutex

优点:可跨进程使用,可重入。
缺点:性能相对较差,开销较大,相对于lock和Monitor,使用起来较复杂。

using System;
using System.Threading;

class Example
{
    private static Mutex mutex = new Mutex();
    private static int counter = 0;

    static void Main()
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Counter: " + counter);
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            mutex.WaitOne();
            counter++;
            mutex.ReleaseMutex();
        }
    }
}

ReaderWriterLockSlim

优点:读写锁定分离,适用于读操作远多于写操作的场景,性能较高。
缺点:使用起来相对复杂,不可跨进程使用。

using System;
using System.Threading;

class Example
{
    private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private static int counter = 0;

    static void Main()
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Counter: " + counter);
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 1000; i++)
        {
            rwLock.EnterWriteLock();
            counter++;
            rwLock.ExitWriteLock();
        }
    }
}

ManualResetEvent

优点:灵活,可以用于多个线程之间的信号传递。
缺点:不是传统意义上的同步锁,只能控制线程的等待和继续,不适用于保护资源的互斥访问。

using System;
using System.Threading;

class Example
{
    private static ManualResetEvent mre = new ManualResetEvent(false);
    private static int counter = 0;

    static void Main()
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        mre.Set();

        t1.Join();
        t2.Join();

        Console.WriteLine("Counter: " + counter);
    }

    static void IncrementCounter()
    {
        mre.WaitOne();

        for (int i = 0; i < 1000; i++)
        {
            counter++;
        }
    }
}

结论

请注意,这些代码示例中的同步方法各有优缺点。在实际应用中,请根据具体需求选择合适的同步方法。

在实际应用中,需要根据需求和场景来选择合适的同步方法。

  • 简单场景下,可以优先考虑使用lock。
  • 如果需要跨进程同步,可以使用Mutex或Semaphore。
  • 在读写操作不平衡的情况下,可以考虑使用ReaderWriterLockSlim。
  • 对于线程之间的信号传递,可以使用ManualResetEvent。
  • 而在需要更多控制和灵活性的场景下,可以考虑使用Monitor。
posted @ 2023-05-09 08:38  yhm138  阅读(107)  评论(0编辑  收藏  举报