当前位置: 首页 > article >正文

C# 中的多线程同步:原子变量、原子操作、内存顺序和可见性

C# 中的多线程同步:原子变量、原子操作、内存顺序和可见性

引言

随着现代计算机系统的发展,多核处理器已经变得非常普遍。在这种环境下,多线程编程成为提高应用程序性能的关键技术之一。然而,多线程编程带来了新的挑战,其中之一就是确保数据在并发访问时的一致性和安全性。本文将探讨 C# 中的多线程同步机制,特别是原子变量、原子操作、内存顺序和可见性,并通过代码示例来演示如何使用这些工具来构建健壮的并发程序。

原子操作与原子变量

在多线程环境中,原子操作是指那些不可中断的操作。这意味着一旦开始执行,该操作就会一直执行到完成,期间不会被其他线程中断。C# 中提供了多种工具来实现原子操作,包括 Interlocked 类和 System.Threading.Atomic 类。

Interlocked

Interlocked 类提供了多个静态方法来执行原子操作,这些方法可以确保在多线程环境中对整型变量的操作是原子的。例如,Interlocked.IncrementInterlocked.Decrement 可以用于安全地增加或减少共享变量的值。

示例代码
using System;
using System.Threading;

class Program
{
    private static int counter = 0;
    private static CountDownEvent allDone = new CountDownEvent(5); // 五个线程

    static void Increment()
    {
        for (int i = 0; i < 100000; i++)
        {
            Interlocked.Increment(ref counter);
        }
        allDone.Signal(); // 通知检查线程,本线程已完成递增
    }

    static void Check()
    {
        allDone.Wait(); // 等待所有线程完成
        int finalValue = counter;
        Console.WriteLine($"Final counter value: {finalValue}");

        // 验证最终值是否等于预期
        int expectedValue = 5 * 100000; // 五个线程,每个线程递增100000次
        if (finalValue == expectedValue)
        {
            Console.WriteLine("Counter incremented correctly.");
        }
        else
        {
            Console.WriteLine("Counter did not increment correctly.");
        }
    }

    static void Main(string[] args)
    {
        var threads = new Thread[5];
        for (int i = 0; i < 5; i++)
        {
            threads[i] = new Thread(new ThreadStart(Increment));
            threads[i].Start();
        }

        var checkThread = new Thread(new ThreadStart(Check));
        checkThread.Start();

        foreach (var thread in threads)
        {
            thread.Join();
        }

        checkThread.Join();
    }
}

System.Threading.Atomic

从 C# 8.0 开始,引入了 System.Threading.Atomic 类,该类提供了原子类型的实现,类似于 C++ 中的 std::atomic。使用 System.Threading.Atomic 类可以更加方便地处理原子操作。

示例代码
using System;
using System.Threading;

class Program
{
    private static int counter = 0;
    private static CountDownEvent allDone = new CountDownEvent(5); // 五个线程

    static void Increment()
    {
        for (int i = 0; i < 100000; i++)
        {
            Interlocked.Increment(ref counter);
        }
        allDone.Signal(); // 通知检查线程,本线程已完成递增
    }

    static void Check()
    {
        allDone.Wait(); // 等待所有线程完成
        int finalValue = counter;
        Console.WriteLine($"Final counter value: {finalValue}");

        // 验证最终值是否等于预期
        int expectedValue = 5 * 100000; // 五个线程,每个线程递增100000次
        if (finalValue == expectedValue)
        {
            Console.WriteLine("Counter incremented correctly.");
        }
        else
        {
            Console.WriteLine("Counter did not increment correctly.");
        }
    }

    static void Main(string[] args)
    {
        var threads = new Thread[5];
        for (int i = 0; i < 5; i++)
        {
            threads[i] = new Thread(new ThreadStart(Increment));
            threads[i].Start();
        }

        var checkThread = new Thread(new ThreadStart(Check));
        checkThread.Start();

        foreach (var thread in threads)
        {
            thread.Join();
        }

        checkThread.Join();
    }
}

内存顺序和可见性

在多线程环境中,内存顺序和可见性是非常重要的概念。内存顺序指的是内存操作的顺序,而可见性则确保一个线程对共享数据的修改对其他线程可见。

内存顺序

内存顺序决定了内存操作的执行顺序,这对于确保数据的一致性至关重要。在 C# 中,Interlocked 类提供了不同的内存顺序选项,如 MemoryOrderReleaseMemoryOrderAcquireMemoryOrderSeqCst 等。

示例代码
using System;
using System.Threading;

class Program
{
    private static int flag = 0;
    private static int counter = 0;

    static void Writer()
    {
        Interlocked.Exchange(ref flag, 1, MemoryOrder.Release); // 设置 flag
        Interlocked.Exchange(ref counter, 42, MemoryOrder.Release); // 设置 counter
    }

    static void Reader()
    {
        while (Interlocked.CompareExchange(ref flag, 0, 1, MemoryOrder.Acquire) != 1)
        {
            Thread.Yield(); // 使当前线程放弃执行权
        }
        Console.WriteLine("Counter value: " + Interlocked.Exchange(ref counter, 0, MemoryOrder.Acquire));
    }

    static void Main(string[] args)
    {
        var writerThread = new Thread(Writer);
        var readerThread = new Thread(Reader);

        writerThread.Start();
        readerThread.Start();

        writerThread.Join();
        readerThread.Join();

        Console.WriteLine("Final counter value: " + counter);
    }
}

内存可见性

内存可见性确保一个线程对共享数据的修改对其他线程可见。在 C# 中,使用 volatile 关键字可以标记一个变量,确保编译器不会对该变量进行优化,从而保证在多线程环境中的内存可见性。但是,volatile 本身并不提供原子性,仅保证内存可见性。

示例代码
using System;
using System.Threading;

class Program
{
    private static volatile bool flag = false;
    private static int counter = 0;

    static void Writer()
    {
        flag = true;
        counter = 42;
    }

    static void Reader()
    {
        while (!flag)
        {
            Thread.Yield(); // 使当前线程放弃执行权
        }
        Console.WriteLine("Counter value: " + counter);
    }

    static void Main(string[] args)
    {
        var writerThread = new Thread(Writer);
        var readerThread = new Thread(Reader);

        writerThread.Start();
        readerThread.Start();

        writerThread.Join();
        readerThread.Join();

        Console.WriteLine("Final counter value: " + counter);
    }
}

结论

多线程编程需要仔细考虑数据的一致性和同步问题。C# 提供了多种工具来帮助开发者构建健壮的并发程序,包括 Interlocked 类、System.Threading.Atomic 类以及 volatile 关键字。通过合理使用这些工具,可以有效地避免数据竞争和其他并发问题,确保程序的正确性和高效性。


通过上述示例和解释,我们看到了如何在 C# 中使用原子变量、原子操作、内存顺序和可见性来构建可靠的多线程应用程序。希望这篇文章能帮助你在开发并发程序时更好地理解和运用这些概念。


http://www.kler.cn/news/304594.html

相关文章:

  • uni-app 聊天界面滚动到消息底部
  • Java后端程序员简单操作Linux系统命令
  • 深度学习——数据预处理,张量降维
  • Qt 边框border - qss样式
  • C++类与对象深度解析(一):从抽象到实践的全面入门指南
  • Linux相关:在阿里云下载centos系统镜像
  • Leetcode Hot 100刷题记录 -Day16(旋转图像)
  • 15.2 定义一个prometheus数据存储使用的pv
  • A Single Generic Prompt forSegmenting Camouflaged Objects
  • java: 程序包org.junit.jupiter.api不存在
  • 数据分析-前期数据处理
  • MacOS Sonoma(14.x) 大写模式或中文输入法下的英文模式,光标下方永远会出现的CapsLock箭头Icon的去除办法
  • Prompt提示词技巧
  • OA项目值用户登入首页展示
  • ArrayList、LinkedList和Vector的区别
  • Python 的分支结构
  • 如何选择适合企业的高效财税自动化软件
  • 桌面应用框架:tauri是后起之秀,赶上electron路还很长。
  • Mysql | 知识 | 理解是怎么加锁的
  • ansible企业实战
  • 高级java每日一道面试题-2024年9月09日-数据库篇-事务提交后数据仍然没有持久化,可能的原因是什么?
  • 海外服务器:开启全球业务的关键钥匙
  • 神经网络的公式推导与代码实现(论文复现)
  • OFDM系统PAPR算法的MATLAB仿真,对比SLM,PTS以及CAF,对比不同傅里叶变换长度
  • Java中的Lambda表达式和Stream API详解
  • NLTK:一个强大的自然语言处理处理Python库
  • Linux python pyinstaller 打包问题
  • 基于React通用的 WebSocket 钩子 useWebSocket
  • 二进制部署ETCD单机版
  • VITS 源码解析2-模型概述