详解C#多线程之线程同步

详解C#多线程之线程同步

前言

在多线程编程中,线程同步是一个非常重要的概念。当多个线程并发执行同一段代码时,由于线程执行顺序和时机的不确定性,可能会导致各种不可预测的结果,比如死锁、竞态条件等问题。因此,为了确保多线程程序的正确性,我们必须使用正确的线程同步机制来协调线程之间的访问。

本文将详细讲解C#中的线程同步机制,包括锁、互斥量、信号量和事件等。

锁是最基本的线程同步机制,通常用于保护临界区(critical section)。临界区是一段需要互斥访问的代码区域,例如对共享资源的读写操作。

C#中的锁可以使用lock关键字来实现。lock语句可以将一段代码标记为临界区,并且确保在任意时刻只有一个线程可以进入这个区域执行代码,其他线程必须等待当前线程退出临界区后才能进入。

下面是一个简单的示例,演示了如何使用lock来实现线程同步:

class Counter {
    private int count = 0;

    public void Increment() {
        lock (this) {
            count++;
        }
    }

    public void Decrement() {
        lock (this) {
            count--;
        }
    }

    public int GetCount() {
        return count;
    }
}

class Program {
    static void Main(string[] args) {
        Counter counter = new Counter();

        Thread t1 = new Thread(() => {
            for (int i = 0; i < 100000; i++) {
                counter.Increment();
            }
        });

        Thread t2 = new Thread(() => {
            for (int i = 0; i < 100000; i++) {
                counter.Decrement();
            }
        });

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

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

        Console.WriteLine(counter.GetCount()); // 应该输出0
    }
}

在上面的例子中,我们定义了一个Counter类,这个类有一个私有的计数器countIncrement方法将计数器加1,Decrement方法将计数器减1,GetCount方法返回计数器的值。注意,IncrementDecrement方法都是使用lock语句来标记临界区,以确保任意时刻只有一个线程可以进入这个区域执行代码。

Main方法中,我们创建了两个新线程t1t2,分别执行IncrementDecrement方法。我们使用Join方法等待这两个线程执行完毕。最后,我们输出计数器的值,这个值应该是0。

互斥量

除了锁,互斥量也是一种常用的线程同步机制。互斥量可以用于实现多个线程之间的互斥访问,保护临界区。

C#中的互斥量可以使用Mutex类来实现。Mutex类是一个系统级别的同步原语,可以跨进程使用。在创建Mutex对象时,需要指定一个名称。如果在同一台计算机上创建了多个名称相同的Mutex对象,这些对象将被视为是互斥的,即只有一个线程可以获得这些对象的锁。

下面是一个示例,演示了如何使用Mutex来实现线程同步:

class Counter {
    private int count = 0;
    private static Mutex mutex = new Mutex();

    public void Increment() {
        mutex.WaitOne();
        count++;
        mutex.ReleaseMutex();
    }

    public void Decrement() {
        mutex.WaitOne();
        count--;
        mutex.ReleaseMutex();
    }

    public int GetCount() {
        return count;
    }
}

class Program {
    static void Main(string[] args) {
        Counter counter = new Counter();

        Thread t1 = new Thread(() => {
            for (int i = 0; i < 100000; i++) {
                counter.Increment();
            }
        });

        Thread t2 = new Thread(() => {
            for (int i = 0; i < 100000; i++) {
                counter.Decrement();
            }
        });

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

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

        Console.WriteLine(counter.GetCount()); // 应该输出0
    }
}

在上面的例子中,我们使用了一个静态的Mutex对象mutex,这个对象在整个程序中是共享的。在IncrementDecrement方法内部,我们分别调用了WaitOneReleaseMutex方法来获取和释放锁。

与锁不同的是,将mutex.WaitOne()mutex.ReleaseMutex()包含在锁定块内是不合法的。因为Mutex并非C#语言级别的构造,而是由系统提供的同步原语,使用WaitOne方法可以跨越方法边界对代码进行同步。

信号量

信号量是一种常用的线程同步机制,可以用于控制多个线程之间的并发数量。信号量可以看作是一个计数器,当信号量的计数器大于零时,允许某个线程继续执行;当计数器等于零时,阻塞该线程。当另一个线程释放了一个信号量时,计数器加1,唤醒一个(或多个)被阻塞的线程。

C#中的信号量可以使用Semaphore类来实现。Semaphore类有两个构造函数:一个接受一个初始计数器,另一个接受一个初始计数器和一个最大计数器。Semaphore.WaitOne方法用来获取信号量,Semaphore.Release方法用来释放信号量。

下面是一个示例,演示了如何使用Semaphore来实现线程同步:

class Counter {
    private int count = 0;
    private static Semaphore semaphore = new Semaphore(1, 1);

    public void Increment() {
        semaphore.WaitOne();
        count++;
        semaphore.Release();
    }

    public void Decrement() {
        semaphore.WaitOne();
        count--;
        semaphore.Release();
    }

    public int GetCount() {
        return count;
    }
}

class Program {
    static void Main(string[] args) {
        Counter counter = new Counter();

        Thread t1 = new Thread(() => {
            for (int i = 0; i < 100000; i++) {
                counter.Increment();
            }
        });

        Thread t2 = new Thread(() => {
            for (int i = 0; i < 100000; i++) {
                counter.Decrement();
            }
        });

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

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

        Console.WriteLine(counter.GetCount()); // 应该输出0
    }
}

在上面的例子中,我们使用了一个静态的Semaphore对象semaphore,这个对象在整个程序中是共享的。在IncrementDecrement方法内部,我们分别调用了WaitOneRelease方法来获取和释放信号量。这里我们使用了一个计数器为1的Semaphore对象,表示同时只允许一个线程进入临界区。

事件

事件是一种多线程同步机制,可以用来在多个线程之间传递信号。事件有两种状态:已触发和未触发。已触发的事件等价于一个开关,打开开关后,所有通过WaitOne方法等待该事件的线程都可以开始执行;未触发的事件则表示等待状态,直到有线程通过Set方法将事件触发为止。

C#中的事件可以使用ManualResetEventAutoResetEvent类来实现。这两个类都继承自EventWaitHandle类。两者的区别在于:ManualResetEvent需要显式地调用Reset方法将事件重置到未触发状态;而AutoResetEvent会在一个线程通过WaitOne方法等待该事件时自动触发,无需再调用Set方法。

下面是一个示例,演示了如何使用ManualResetEvent来实现线程同步:

class Counter {
    private int count = 0;
    private static ManualResetEventSlim mre = new ManualResetEventSlim();

    public void Increment() {
        count++;
        mre.Set();
    }

    public void Decrement() {
        count--;
        mre.Set();
    }

    public int GetCount() {
        return count;
    }

    public void WaitForChange() {
        mre.Wait();
        mre.Reset();
    }
}

class Program {
    static void Main(string[] args) {
        Counter counter = new Counter();

        Thread t1 = new Thread(() => {
            Random r = new Random();
            for (int i = 0; i < 1000; i++) {
                counter.Increment();
                Thread.Sleep(r.Next(10));
            }
        });

        Thread t2 = new Thread(() => {
            Random r = new Random();
            for (int i = 0; i < 1000; i++) {
                counter.Decrement();
                Thread.Sleep(r.Next(10));
            }
        });

        Thread t3 = new Thread(() => {
            for (int i = 0; i < 10; i++) {
                counter.WaitForChange();
                Console.WriteLine(counter.GetCount());
            }
        });

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

        t1.Join();
        t2.Join();
        t3.Join();
    }
}

在上面的例子中,我们使用了一个静态的ManualResetEventSlim对象mre,这个对象在整个程序中是共享的。在IncrementDecrement方法中,我们分别将计数器加1或减1,然后调用Set方法来触发事件。在WaitForChange方法中,我们使用Wait方法等待事件的触发,并在事件触发后调用Reset方法将事件重置到未触发状态。在Main函数中,我们创建了三个线程t1t2t3,分别执行IncrementDecrementWaitForChange方法。在t3线程中,我们使用WaitForChange方法等待计数器的变化,并在计数器变化后输出计数器的值。

结语

本文详细讲解了C#中的线程同步机制,包括锁、互斥量、信号量和事件。在编写多线程程序时,根据实际情况选择合适的同步机制非常重要,以确保程序的正确性和健壮性。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解C#多线程之线程同步 - Python技术站

(0)
上一篇 2023年5月15日
下一篇 2023年5月15日

相关文章

  • c#读写App.config,ConfigurationManager.AppSettings 不生效的解决方法

    针对 “c#读写App.config,ConfigurationManager.AppSettings不生效的解决方法” 这个问题,我们可以从以下几个方面入手: 1. 确认App.config格式是否正确 在使用App.config的时候,我们需要确保这个文件名及格式都是正确的,这是一个很容易被忽略的问题。首先,确认你的App.config文件是放在程序的根…

    C# 2023年5月15日
    00
  • C#实现贪吃蛇小游戏

    C#实现贪吃蛇小游戏完整攻略 前言 贪吃蛇作为一款经典的小游戏,大家都玩过。今天我们将借助C#的强大功能,实现一个简单的贪吃蛇小游戏,并将整个过程详细讲解。 准备工作 在开始之前,我们需要先准备好开发C#小游戏的环境。1. 首先需要安装Visual Studio,此处以Visual Studio 2019为例。2. 安装完成后,打开VS,点击”创建新项目”,…

    C# 2023年6月1日
    00
  • C#基于百度AI实现机器翻译功能

    下面是“C#基于百度AI实现机器翻译功能”完整攻略的详细讲解。 1. 准备工作 在开始实现机器翻译功能之前,我们需要先准备好所需要的资源和工具: 1.1 安装百度AI SDK 访问百度AI开放平台并注册账号,然后可以在控制台获取到 api_key 和 secret_key。 百度AI SDK提供了各种AI相关的服务,我们需要安装其中的机器翻译SDK,可以通过…

    C# 2023年5月31日
    00
  • C#无损转换Image为Icon的方法

    下面我将为您详细讲解“C#无损转换Image为Icon的方法”的完整攻略。 介绍 首先,我们需要了解一下什么是ICO格式文件。ICO文件是Windows操作系统中图标的标准格式,它可以保存不同大小和颜色深度的图标。 在C#中,我们可以使用System.Drawing.Imaging命名空间中的Icon和IconInfo类来操作ICO文件。接下来,我将介绍如何…

    C# 2023年6月1日
    00
  • MVC 5 第一章 创建MVC 5 web应用程序

    下面是关于“MVC 5 第一章 创建MVC 5 web应用程序”的完整攻略,主要包含以下内容: 创建MVC 5 web应用程序的步骤 每个步骤所涉及到的具体操作 两条示例说明 1. 创建MVC 5 web应用程序的步骤 创建MVC 5 web应用程序的步骤主要包括以下几个方面: 创建项目 配置项目 创建控制器 创建模型 创建视图 2. 每个步骤所涉及到的具体…

    C# 2023年5月31日
    00
  • ASP.NET 输出图片简单代码

    当我们在ASP.NET中需要向客户端输出图片时,可以使用以下简单的代码实现。 步骤1:在ASP.NET页面中添加Image控件 我们需要在ASP.NET页面中添加一个Image控件,该控件用于显示我们准备输出的图片。 <asp:Image ID="imgOutput" runat="server"/> 步骤…

    C# 2023年5月31日
    00
  • C# Count:获取集合中的元素数

    C#中的Count方法是用来统计序列中满足指定条件的元素个数的方法。它属于LINQ扩展方法,可以用于IEnumerable泛型接口的所有实现类。下面我们将详细讲解C# Count方法的使用。 基本语法 Count方法的基本语法如下: int count = source.Count(); 其中,source表示需要统计元素个数的序列。Count方法返回一个i…

    C# 2023年4月19日
    00
  • ASP.NET Core中间件会话状态读写及生命周期示例

    ASP.NET Core中间件会话状态读写及生命周期示例 在ASP.NET Core应用程序中,会话状态是一种在多个请求之间保持数据的机制。在本攻略中,我们将详细讲解如何在ASP.NET Core中间件中读写会话状态,并介绍会话状态的生命周期。 步骤一:启用会话状态 在ASP.NET Core应用程序中启用会话状态,您需要在Startup.cs文件中调用Ad…

    C# 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部