详解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#异步编程由浅入深(一)

    下面是“C#异步编程由浅入深(一)”完整攻略的讲解: 异步编程的定义 异步编程(Asynchronous Programming)是指在执行耗时操作时,允许其他操作同时执行的编程方式。它可以让主程序不被阻塞,提高程序的并发能力和响应性能。 异步编程的模型 C#中的异步编程采用任务(Task)模型,它由以下几部分组成: Task的定义 Task是一个基本的异步…

    C# 2023年6月6日
    00
  • C# 中的List.Sort()–集合排序方法全面解析

    C#中的List.Sort()–集合排序方法全面解析 1. 概述 在C#开发中,List 是常见的一种集合类型,其提供了一个 Sort() 方法来实现对集合的排序。本篇文章主要介绍 List 中的 Sort() 方法的功能及相关使用技巧。 2. 功能说明 List 中的Sort()方法用于对集合进行排序。默认情况下,Sort()方法按照升序对集合进行排序,…

    C# 2023年5月15日
    00
  • C#创建自签名认证文件的方法

    下面为您详细讲解C#创建自签名认证文件的方法的完整攻略。 什么是自签名认证文件 自签名认证文件是用来对软件代码进行签名的一种证书,用于保证软件代码的来源和完整性。 C#中也支持使用自签名认证文件对程序集进行签名,使程序能够在运行时通过CAS(代码访问安全性)校验。 创建自签名认证文件的步骤 第一步:生成证书文件 可以使用makecert工具来生成自签名证书文…

    C# 2023年6月1日
    00
  • C# params可变参数的使用注意详析

    C# params 可变参数的使用注意详析 什么是 params? C# 中的 params 关键字可以让我们定义一个可变长度参数列表。使用 params 关键字的方法可以接受零个或多个参数,参数在方法内部被视为数组,可以像普通数组一样进行访问。 使用 params 关键字定义的参数必须是方法的最后一个参数,而且一个方法只能拥有一个 params 关键字定义…

    C# 2023年5月15日
    00
  • Oracle数据远程连接的四种设置方法和注意事项

    Oracle数据远程连接的四种设置方法和注意事项 Oracle数据库是一款功能强大的数据库产品,可进行本地和远程连接。通过远程连接,可以让多个客户端连接到同一个数据库实例,实现共享数据的目的。在本文中,我们将详细讲解Oracle数据远程连接的四种设置方法和注意事项。 1. 设置监听器(Listener) 监听器是Oracle数据库与其他应用程序之间通信的重要…

    C# 2023年5月15日
    00
  • asp.net中资源文件的使用

    当我们开发ASP.NET应用程序时,使用多语言资源文件是一种良好的实践。本文将为你介绍ASP.NET应用程序中资源文件的用法。 资源文件的定义和分类 资源文件是什么? 资源文件(Resource File)是指保存一个或多个文本字符串、图像、音频或其他类型数据的文本文件。 .NET Framework 提供了一种能够以有组织的方式存储、访问和管理资源的方式,…

    C# 2023年5月31日
    00
  • 测试框架nunit之assertion断言使用详解

    测试框架NUnit之Assertion断言使用详解 什么是Assertion断言? Assertion断言在编程中通常被称为“断言”或“断言语句”,是一种旨在保证代码正在执行所期望的工作的技术。在测试代码中,Assertion断言通常用于验证测试是否按照预期执行。 如何使用Assertion断言? NUnit是一种广泛使用的测试框架,可以使用Assertio…

    C# 2023年5月15日
    00
  • C#从数据库读取数据到DataSet并保存到xml文件的方法

    下面是详细讲解“C#从数据库读取数据到DataSet并保存到xml文件的方法”的完整攻略: 步骤1:连接数据库并读取数据 首先,需要在代码中连接数据库,从中读取数据,并将其存储在内存中的 DataSet 中。可以使用 SqlConnection 和 SqlDataAdapter 类来实现这个步骤。下面是一个示例代码: string connectionStr…

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