并发编程实现

一、并行编程

1、Parallel 类

Parallel类是System.Threading.Tasks命名空间中的一个重要类,它提供数据并行和任务并行的高级抽象。

For和ForEach

Parallel类下的For和ForEach对应着普通的循环和遍历(普通的for和foreach),但执行时会尝试在多个线程上同时处理循环迭代。

//For
Parallel.For(0, 10, i =>
{
    Console.WriteLine($"我是:{i}。我的线程ID是:{Thread.CurrentThread.ManagedThreadId}");
});
//ForEach
List<string> items = new List<string> { "A", "B", "C", "D", "E" };
Parallel.ForEach(items, item =>
{
    Console.WriteLine($"我是:{item}。我的线程ID是:{Thread.CurrentThread.ManagedThreadId}");
});

与普通For输出后的区别(右为普通For循环),我们可以看到Parallel类下For输出的顺序不是递增的,这是因为它是在不同的线程中执行所导致的。

 

Invoke

尽可能并行执行提供的每个操作,即方法调用。

Action action1 = () => Console.WriteLine("你好");
Action action2 = () => Console.WriteLine("不好");
Parallel.Invoke(action1, action2);

2、PLINQ

PLINQ为Parallel LINQ的缩写,在LINQ中允许你利用多核处理器并行处理数据,PLINQ会自动将查询拆分成多个部分,并在多个线程上并行执行这些部分。

AsParallel()

将顺序的LINQ查询转换为并行的查询,允许利用多核处理器并行处理数据集合,从而加速查询的执行,当调用该方法时,LINQ查询会转换为PLINQ查询。 

int[] data = Enumerable.Range(0, 100).ToArray();
var query =from i in data.AsParallel()
           where i%10==0
           select i;

并行度

在默认的情况下,PLINQ会使用计算机上所有的处理器,使用WithDegreeOfParallelism用于指定用于并行查询执行的处理器的最大数量,即同时执行的任务数量。

int[] data = Enumerable.Range(0, 100).ToArray();
var query =from i in data.AsParallel().WithDegreeOfParallelism(3)
           where i%10==0
           select i;

排序

默认情况下,PLINQ 不保证输出顺序与输入顺序一致。如果需要保持顺序,可以使用AsOrdered()方法,但是调用该方法时,PLINQ 将尝试在并行处理的同时保留元素的顺序,这也就意味着性能可能会下降。

int[] data = Enumerable.Range(0, 100).ToArray();
var query =from i in data.AsParallel().AsOrdered()
           where i%10==0
           select i;

ForAll()

用于并行地遍历查询结果集,并对每个元素执行一个指定的操作,并行执行,可以充分利用多核处理器的性能优势,加快处理速度,但并不保证操作的执行顺序与原始数据集中的顺序一致。

query.ForAll(res =>
{
    Console.WriteLine(res);
});

二、数据并发控制

1、lock(锁)

lock关键字用于确保当一个线程进入代码的临界区时,其他线程不会进入该临界区,这有助于防止多个线程同时访问同一资源,可能导致数据不一致以及数据被破坏的问题。(lock关键字通常与对象一起使用,该对象用作锁定的目标,通常称为“锁对象”。)

public class Account
{
    private Object thisLock = new Object();

    public void Withdraw()
    {
        // 这是一个临界区,只有一个线程可以进入  
        lock (thisLock)
        {
            try
            {
                //模拟工作
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine($"我是{i},我的线程ID:{Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(500);//模拟延迟
                }
            }
            finally
            {
                Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}被释放了");
                // 确保锁被释放,即使发生异常  
            }
        }
    }
}

当我们使用两个或者多个线程同时调用该方法时, 只会有一个线程拿到锁对象进入到临界区,其余的线程会形成阻塞态,一直等待该锁被释放然后进入到临界区(或者任务超时该线程摧毁)。

Account account = new Account();
//创建线程1任务
Task task1 = Task.Run(() => account.Withdraw());
//创建线程2任务
Task task2 = Task.Run(() => account.Withdraw());
//等待两个任务完成
Task.WaitAll(task1, task2);

2、Monitor类

Monitor类允许线程安全地访问共享资源,是System.Threading命名空间的一部分,并且提供了比lock更底层和更灵活的功能,防止多个线程同时访问某个代码段或资源导致数据不一致以及数据被破坏的问题。

public class Account
{
    private Object thisLock = new Object();

    public void Withdraw()
    {
        //获取锁
        Monitor.Enter(thisLock);
        try
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"我是{i},我的线程ID:{Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(500);
            }
        }
        finally
        {
            Console.WriteLine($"线程{Thread.CurrentThread.ManagedThreadId}被释放了");
            //释放锁
            Monitor.Exit(thisLock);
            // 确保锁被释放,即使发生异常  
        }
    }
}

注意:一个线程获取到了锁,那么其他线程就会形成阻塞态,如果该线程的任务完成之后,没有释放锁,那么后面的线程就一直处于阻塞态的状态,形成死锁。

TryEnter

指定毫秒内获取锁,如果获取到锁则返回true,如果没有获取到锁,则返回false,该方法通常配合一些逻辑执行,任务完成后同样需要Exit来释放锁,否则会造成死锁。

bool MonitorBool = Monitor.TryEnter(thisLock,5000);

3、ReaderWriterLockSlim类

它允许多个读取者同时访问资源,但在写入时则独占资源,即不允许其他读取者或写入者同时访问(读共享写独享)。

1、基本读写锁

读锁(EnterReadLock和ExitReadLock):当多个线程需要同时读取共享资源时,可以使用读锁。多个线程可以同时持有读锁,这意味着它们可以同时读取共享资源,但在此期间,任何线程都不能获得写锁。

写锁(EnterWriteLock和ExitWriteLock):当线程需要修改共享资源时,它必须获得写锁。在写锁被持有的期间,其他线程既不能获得读锁也不能获得写锁,确保共享资源的单独访问。

public class Account
{
    private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private int sharedData = 0;

    //写操作
    public void WriteData(int value)
    {
        //获取写入锁
        rwLock.EnterWriteLock();
        try
        {
            //执行写入
            sharedData = value;
            Thread.Sleep(2000);//模拟延迟
        }
        finally
        {
            Console.WriteLine($"{sharedData}写入完成,我的线程ID:{Thread.CurrentThread.ManagedThreadId}");
            //释放写入锁
            rwLock.ExitWriteLock();
        }
    }
    //读操作
    public int ReadData()
    {
        //获取读取锁
        rwLock.EnterReadLock();
        try
        {
            Thread.Sleep(2000);//模拟延迟
            return sharedData;
        }
        finally
        {
            Console.WriteLine($"读取完成,我的线程ID:{Thread.CurrentThread.ManagedThreadId}");
            //释放读取锁
            rwLock.ExitReadLock();
        }
    }
}

2、升级读写锁

升级读锁(EnterUpgradeableReadLock和ExitUpgradeableReadLock):当线程以可升级的方式获取读锁时,它表示该线程可能随后需要升级到写锁,在此期间,其他线程不能获得写锁,但可以获得读锁。

升级写锁(EnterWriteLock):当持有可升级的读锁的线程决定需要修改共享资源时,它可以调用 EnterWriteLock 方法来升级锁,而无需先释放读锁,这将阻塞其他所有尝试获取读锁或写锁的线程,直到写锁被释放。

public class Account
{
    private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private int sharedData = 0;

    public void UpgradeableReadAndWriteData(int value)
    {
        //进入升级读取模式
        rwLock.EnterUpgradeableReadLock();
        try
        {
            // 执行读操作  
            int currentData = sharedData;

            // 假设基于读取的数据,决定需要修改数据  
            if (currentData != value)
            {
                // 升级到写锁,而不释放读锁  
                rwLock.EnterWriteLock();
                try
                {
                    // 在写锁下,执行写操作  
                    sharedData = value;
                    Console.WriteLine($"{sharedData}写入,我的线程ID是{Thread.CurrentThread.ManagedThreadId}");
                }
                finally
                {
                    Console.WriteLine($"退出写锁,我的线程ID是{Thread.CurrentThread.ManagedThreadId}");
                    // 退出写锁,但保持读锁  
                    rwLock.ExitWriteLock();
                }
            }
        }
        finally
        {
            Console.WriteLine($"退出升级读锁,我的线程ID是{Thread.CurrentThread.ManagedThreadId}");
            // 退出可升级的读锁  
            rwLock.ExitUpgradeableReadLock();
        }
    }

}

4、Concurrent(并发集合)

BlockingCollection<T>

提供了一种线程安全的方式来在多个生产者线程和消费者线程之间共享数据。当集合为空时,尝试从中取数据的操作会被阻塞,直到有数据可用。同样地,当集合已满(如果设置了容量限制)时,尝试添加数据的操作也会被阻塞。

using System.Collections.Concurrent;

BlockingCollection<int> collection = new BlockingCollection<int>();

// 启动生产者任务  
Task producerTask = Task.Run(() =>
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("生产者生产数据: " + i);
        collection.Add(i); // 将数据添加到集合中  
        Thread.Sleep(1000); // 模拟耗时操作  
    }

    // 通知消费者没有更多的数据将要添加  
    collection.CompleteAdding();
});

// 启动消费者任务  
Task consumerTask = Task.Run(() =>
{
    foreach (var item in collection.GetConsumingEnumerable())
    {
        Console.WriteLine("消费者消费数据: " + item);
        Thread.Sleep(500); // 模拟耗时操作  
    }
});

// 等待生产者和消费者任务完成  
Task.WaitAll(producerTask, consumerTask);

常用类:

ConcurrentBag<T>

这是一个无序的线程安全集合,允许线程安全地添加和移除元素。

ConcurrentDictionary<TKey, TValue>

这是一个线程安全的字典,支持线程安全地添加、移除和访问键值对。

ConcurrentQueue<T>

这是一个线程安全的先进先出(FIFO)集合,支持线程安全地入队和出队操作。

ConcurrentStack<T>

这是一个线程安全的后进先出(LIFO)集合,支持线程安全地推入和弹出操作。

Partitioner<TSource>

用于将数据划分为多个分区,以便并行处理。这通常与TPL(Task Parallel Library)一起使用,以便在多个线程或任务上并行处理数据。

OrderablePartitioner<TSource>

这是一个特殊的分区器,它保留了元素的顺序,允许在并行处理的同时保持元素的顺序。

三、异步流

允许你以异步的方式处理数据流,当你需要在不阻塞线程或其他重要线程的情况下处理大量数据时使用。关键字为IAsyncEnumerable<T>,它允许你以异步的方式枚举数据序列,而不需要一次性加载所有数据到内存中。

public class Account
{
    public async IAsyncEnumerable<int> GenerateAsyncStream()
    {
        for (int i = 0; i < 10; i++)
        {
            await Task.Delay(1000);
            yield return i;
            yield return i + 1;
        }
    }
}
IAsyncEnumerable<int> asyncStream = new Account().GenerateAsyncStream();

await foreach (int i in asyncStream)
{
    Console.WriteLine(i);
}

常见的并发编程方法还有异步编程和多线程编程等。 

四、并发的注意

使用并发编程的时候应该避免以下及其其他问题的出现,通常并发编程的错误都是因为多个线程同时访问和修改共享资源,或者由于线程之间的同步和协调不当导致的。

1、竞态条件

竞态条件发生在两个或多个线程同时访问共享资源,并且它们的访问顺序会影响程序的结果,在计算共享数据时,由于多个线程计算顺序的不同,导致最终计算的结果也不同导致的。

2、死锁

当两个或多个线程相互等待对方释放资源时,导致所有线程都无法继续执行,这通常是因为线程间的锁顺序不一致导致的。

3、活锁

当线程们都在忙于响应其他线程的动作,但无法完成它们自己的任务时导致的。

4、饥饿

当其他线程一直占用资源而得不到释放时,某些线程长时间得不到执行的机会而导致的。

5、数据不一致

当多个线程没有正确同步对共享数据的访问时,可能会导致数据的不一致状态,这可能是因为一个线程正在读取数据,而另一个线程同时修改了这些数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/605961.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Blender修改器

修改器 Modifier&#xff0c;对模型进行修改&#xff0c;相当于一个函数。 修改器图标是界面右下角的扳手样式 每个修改器的顶部都有如下样式&#xff0c;从左到右分别为&#xff1a;展开/折叠&#xff0c;修改器类型&#xff0c;修改器名称&#xff0c;编辑模式按钮&#xff…

游戏辅助 -- 某游戏一键端配置

游戏一键端下载地址及安装视频&#xff1a; https://pan.quark.cn/s/e6a373d94707 ​https://pan.quark.cn/s/ef7ab0c48776 准备工作 Vmware虚拟机软件&#xff1a;用于创建和管理虚拟机。 SecureCRT&#xff1a;一款支持SSH的终端仿真程序&#xff0c;用于远程登陆服务器…

SoC系统中AXI4 AXI3兼容性及exclusive access

AXI4和AXI3是高级扩展接口&#xff08;Advanced eXtensible Interface&#xff09;的两个不同版本&#xff0c;它们都是用于SoC&#xff08;System on Chip&#xff09;设计中的总线协议&#xff0c;用于处理器和其它外设之间的高速数据传输。以下是它们之间的一些主要区别&…

vscode设置免密登录远程服务器

文章目录 1. 问题描述2. 解决方案3. 原理 1. 问题描述 当我们使用vscode的ssh连接远程服务器后&#xff0c;过一段时间后&#xff0c;总是要求登录服务器的密码。 这就导致一个麻烦就是: 无论是在公司还是在学校&#xff0c;密码往往不是自己设置的&#xff0c;所以记忆起来就…

利用BACnet分布式IO控制器优化Niagara楼宇自动化系统

在智能建筑领域&#xff0c;随着物联网技术的飞速发展&#xff0c;如何实现高效、灵活且安全的楼宇自动化控制成为了行业关注的焦点。BACnet IP分布式远程I/O模块&#xff0c;作为这一领域的创新成果&#xff0c;正逐渐成为连接智能建筑各子系统的关键桥梁&#xff0c;尤其在与…

蓝桥杯练习系统(算法训练)ALGO-946 Q神的足球赛

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 足球赛上&#xff0c;只见Q神如闪电般的速度带球时而左&#xff0c;时而右&#xff0c;时而前&#xff0c;时而后&#xff…

带你入门React

目录 前言一&#xff0c;基本配置1.1 环境搭建1.2 页面初始化渲染二&#xff0c;基础学习2.1 结构与样式开发2.2 数据展示2.3 行内样式2.4 条件渲染2.5 列表渲染2.6 点击事件 三&#xff0c;页面更新3.1 组件数据3.2 组件数据共享 总结 前言 笔者之前的工作经验都局限于Vue&am…

pandas快速使用

DataFrame介绍 Dateframe结构和列表类似&#xff0c;区别是对于DataFrame的每一列和每一行均有一个标签。例如以下数据&#xff0c; 上述数据中&#xff0c;日期作为每行的标签。a、b、c、d、e分别是每列的标签 生成连续日期数据 使用方法date_range()&#xff0c;该方法有两…

Lazada商品详情API接口:深度解析与应用

前言 在当今电子商务的繁荣时代&#xff0c;对于电商平台来说&#xff0c;提供一套高效、稳定的API接口是非常重要的。Lazada&#xff0c;作为东南亚领先的电商平台之一&#xff0c;其API接口体系为卖家、开发者以及第三方服务提供了丰富的功能和数据支持。其中&#xff0c;商品…

邦注科技 模具保护器 CCD电子眼 专业工业视觉检测设备

模具保护器是一种用于保护模具的设备&#xff0c;可以在塑料压铸和冲床等加工过程中起到保护模具的作用。以下是关于模具保护器在保护塑料压铸和冲床模具方面的应用&#xff1a; 塑料压铸模具保护器&#xff1a; 防止碰撞&#xff1a;在塑料压铸过程中&#xff0c;模具可能会…

初识C++ · 内存管理

目录 1 C/C的内存分布 2 C语言的内存管理 3 C的内存管理 4 operator new 和 operator delete 5 定位new 1 C/C的内存分布 语言不同&#xff0c;内存分布是相同的&#xff0c;对于局部变量都是放在栈上&#xff0c;全局变量都是放在静态区&#xff08;数据段&#xff09;&…

jvm重要参数可视化和线上问题排查

jvm重要参数可视化和线上问题排查 目标jvm参数分类(了解)运行时数据区相关的&#xff08;jdk1.8&#xff09;处理 OOM 相关的垃圾回收器相关的GC 日志记录相关的意义,默认值,调优原则&#xff08;重要&#xff0c; 待拆分&#xff09; 排查 OOM 流程 和 常见原因参考文章 目标 …

基于C语言中的类型转换,C++标准创造出了更加可视化的类型转换

目录 前言 一、 C语言中的类型转换 二、为什么C需要四种类型转换 三、C中新增的四种强制类型转换操作符以及它们的应用场景 1.static_cast 2.reinterpret_cast 3.const_cast 4.dynamic_cast 前言 在C语言中&#xff0c;如果赋值运算符左右两侧的类型不同&#xff0c;或者…

短视频矩阵系统贴牌---saas源头开发

一、短视频矩阵运营注意事项&#xff1a; 如&#xff1a;房产行业 短视频矩阵运营是一个系统化的项目&#xff0c;涉及多个平台和账号的管理&#xff0c;以及内容的创作、发布和优化等多个方面。 以下是短视频矩阵运营的注意事项文档的概要以及结果运营数据 一周持续运营量 二…

uni-app 多列picker切换列显示对应内容

html部分&#xff1a; <view class"uni-list"><view class"uni-list-cell"><view class"uni-list-cell-left">选择用户</view><view class"uni-list-cell-db"><picker mode"multiSelector"…

【JavaWeb】网上蛋糕商城后台-类目管理,退出

概念 本文讲解和实现类目管理和管理员的退出功能。 类目列表信息 点击类目管理&#xff0c;向服务器发送请求/admin/type_list 在servlet包中创建AdminTypeListServlet类&#xff0c;获得所有商品分类 package servlet;import model.Type; import service.TypeService;impo…

网站localhost和127.0.0.1可以访问,本地ip不可访问解决方案

部署了一个网站, 使用localhost和127.0.0.1加端口号可以访问, 但是使用本机的ip地址加端口号却不行. 原因可能有多种. 可能的原因: 1 首先要确认是否localhost对应的端口是通的(直接网址访问), 以及你无法访问的那个本机ip是否正确(使用ping测试)&#xff1b; 2 检查本机的防火…

堆的基本操作(c语言实现)

1.堆的基本操作 1.1定义堆 typedef int HPDataType;//堆中存储数据的类型typedef struct Heap {HPDataType* a;//用于存储数据的数组int size;//记录堆中已有元素个数int capacity;//记录堆的容量 }HP;1.2初始化堆 然后我们需要一个初始化函数&#xff0c;对刚创建的堆进行初…

软件测试开发之 职业发展必备 能力模型解析

为什么要了解能力模型 王阳明曾在《传习录》中提到过一个思想&#xff1a;以终为始。所谓“以终为始”&#xff0c;意味着在行动的开始阶段就要考虑到最终的目标和结果&#xff0c;以此来指导自己的行动和选择。那么如果我们想在自己的行业内获取好的职业发展&#xff0c;第一…

Meta更低的训练成本取得更好的性能: 多token预测(Multi-Token Prediction)

Meta提出了一种透过多token预测(Multi-token Prediction)来训练更好、更快的大型语言模型的方法。这篇论文的重点如下: 训练语言模型同时预测多个未来的token,可以提高样本效率(sample efficiency)。 在推论阶段,使用多token预测可以达到最高3倍的加速。 论文的主要贡献包括: …
最新文章