详解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# WORD操作实现代码

    下面是详细的C# WORD操作实现代码攻略。 环境准备 安装Visual Studio 安装微软官方提供的Office插件 Word操作示例 示例1:创建新的Word文档并添加内容 using Word = Microsoft.Office.Interop.Word; Word.Application wordApp = new Word.Applicati…

    C# 2023年5月15日
    00
  • C#递归算法和排列算法

    C#递归算法和排列算法 什么是递归算法? 递归算法是一种在函数中调用自身的算法。具有以下特征:- 一个问题可以被分解成几个相同的子问题;- 分解出来的子问题和原问题的解法方式一样;- 递归算法必须要有终止条件。 递归算法在程序设计中应用非常广泛,尤其在树形数据结构的遍历、图形搜索、分治法等方面都有很好的应用。 递归算法示例 下面是一个实现阶乘计算的递归算法:…

    C# 2023年6月7日
    00
  • Unity通过脚本创建网格Mesh的方法

    当我们需要在Unity中动态创建网格(mesh)的时候,通常都是通过代码来实现。如何在脚本中创建网格呢?接下来,我将为大家详细介绍Unity通过脚本创建网格Mesh的方法,希望能帮到大家。 1. 基础网格Mesh的创建 以下是创建基础网格Mesh的步骤: 创建空GameObject作为网格物体的容器。 创建一个MeshFilter组件并将其附加到新GameO…

    C# 2023年6月3日
    00
  • 详解C#读写Excel的几种方法

    标题:详解C#读写Excel的几种方法 正文: 在C#中,常常需要读写Excel的操作,本文将详细解释几种常用的方法。 第一种方法:使用OLEDB读写Excel 首先需要在引用中添加Microsoft.Office.Interop.Excel库。 使用OleDbConnection建立连接,读取需要使用SELECT语句,将数据存入DataSet中,写入Exc…

    C# 2023年6月2日
    00
  • WPF实现类似360安全卫士界面的程序源码分享

    WPF(Windows Presentation Foundation)是一种用于创建Windows桌面应用程序的技术。本文将介绍如何使用WPF实现类似360安全卫士界面的程序源码分享的完整攻略。 步骤一:创建WPF项目 首先,需要创建一个WPF项目。可以使用Visual Studio创建一个新的WPF项目。在创建项目时,可以选择“WPF应用程序”模板。 步…

    C# 2023年5月15日
    00
  • C#实现CSV文件读写的示例详解

    C#实现CSV文件读写的示例详解 1. CSV文件概述 CSV是指逗号分隔符(Comma-Separated Values)文件,它是一种常见的纯文本格式,用于将简单的数据表格导出为电子表格应用程序支持的格式。在电子表格软件中,CSV通常仅用于导出和导入数据。 CSV文件用逗号分隔字段,每条记录占一行,每个字段的值之间用逗号隔开,有一些常见的规则:* 逗号是…

    C# 2023年5月15日
    00
  • React项目中fetch实现跨域接收传递session的解决方案

    React项目中fetch实现跨域接收传递session的解决方案 在 React 项目中,使用 fetch 发送跨域请求时,如果需要接收和传递 session,需要进行一些特殊的处理。本攻略将介绍如何在 React 项目中使用 fetch 实现跨域接收传递 session 的解决方案。 解决方案 以下是在 React 项目中使用 fetch 实现跨域接收传…

    C# 2023年5月17日
    00
  • C#编写一个简单记事本功能

    下面是C#编写一个简单记事本功能的完整攻略。 1. 创建窗体和控件 首先创建一个新的Windows Form应用程序。接着,在窗体上拖动一个文本框控件,一个菜单栏控件和一个文件对话框控件。 2. 实现文件打开和保存功能 双击菜单栏的“打开”按钮,在代码中实现打开文件对话框的功能,并将选择的文件内容读取到文本框控件中。示例如下: private void op…

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