并发编程实现
一、并行编程
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==0select i;
并行度
在默认的情况下,PLINQ会使用计算机上所有的处理器,使用WithDegreeOfParallelism用于指定用于并行查询执行的处理器的最大数量,即同时执行的任务数量。
int[] data = Enumerable.Range(0, 100).ToArray();
var query =from i in data.AsParallel().WithDegreeOfParallelism(3)where i%10==0select i;
排序
默认情况下,PLINQ 不保证输出顺序与输入顺序一致。如果需要保持顺序,可以使用AsOrdered()方法,但是调用该方法时,PLINQ 将尝试在并行处理的同时保留元素的顺序,这也就意味着性能可能会下降。
int[] data = Enumerable.Range(0, 100).ToArray();
var query =from i in data.AsParallel().AsOrdered()where i%10==0select 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、数据不一致
当多个线程没有正确同步对共享数据的访问时,可能会导致数据的不一致状态,这可能是因为一个线程正在读取数据,而另一个线程同时修改了这些数据。
相关文章:

并发编程实现
一、并行编程 1、Parallel 类 Parallel类是System.Threading.Tasks命名空间中的一个重要类,它提供数据并行和任务并行的高级抽象。 For和ForEach Parallel类下的For和ForEach对应着普通的循环和遍历(普通的for和foreach),但执行时会尝试在多个线程上…...

基于EBAZ4205矿板的图像处理:12图像二值化(阈值可调)
基于EBAZ4205矿板的图像处理:12图像二值化(阈值可调) 我的项目是基于EBAZ4205矿板的阈值可调的图像阈值二值化处理,可以通过按键调整二值化的阈值,key1为阈值加1,key4为阈值减1,key2为阈值加10,key5为阈值…...

人大金仓数据库报com.kingbase8.util.KSQLException: 致命错误: 用户 “SYSTEM“ Password 认证失败
com.kingbase8.util.KSQLException: 致命错误: 用户 “SYSTEM” Password 认证失败 解决办法: 问题在于用户权限只不足,相关配置文件在一般在 /data/sys hba.conf,修改IPV4 local connections选项中的改为trust。...

文件加密软件哪个好?文件加密软件排行榜前十名(好用软件推荐)
文件加密软件哪个好?这是许多个人和企业用户在面临数据保护需求时所关心的问题。随着数字化时代的推进,数据安全问题日益凸显,文件加密软件成为了保护数据安全的重要手段。本文将为您介绍当前市场上排名前十的文件加密软件,帮助您…...

Netty的第一个简单Demo实现
目录 说明需求ClientServer写法总结 实现运行 说明 Netty 的一个练习,使用 Netty 连通 服务端 和 客户端,进行基本的通信。 需求 Client 连接服务端成功后,打印连接成功给服务端发送消息HelloServer Server 客户端连接成功后࿰…...

K8S 哲学 - 服务发现 services
apiVersion: v1 kind: Service metadata:name: deploy-servicelabels:app: deploy-service spec: ports: - port: 80targetPort: 80name: deploy-service-podselector: app: deploy-podtype: NodePort service 的 endPoint (ep) 主机端口分配方式 两…...

Springboot工程创建
目录 一、步骤 二、遇到的问题及解决方案 一、步骤 打开idea,点击文件 ->新建 ->新模块 选择Spring Initializr,并设置相关信息。其中组为域名,如果没有公司,可以默认com.example。点击下一步 蓝色方框部分需要去掉,软件包…...

日本站群服务器的优点以及适合该服务器的业务类型?
日本站群服务器的优点以及适合该服务器的业务类型? 日本站群服务器是指位于日本地区的多个网站共享同一台服务器的架构。这种服务器架构有着诸多优点,使其成为许多企业和网站管理员的首选。以下是日本站群服务器的优点以及适合该服务器的业务类型的分析࿱…...

堆的应用2——TOPK问题
TOPK问题 TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。 比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 情况1——数据量小 对于Top-K问题,能想到的最简单直接的方式就…...
leetcode-5. 最长回文子串
题目描述 给你一个字符串 s,找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。 示例 1: 输入:s "babad" 输出:"bab" 解释:"aba"…...
【Flask 系统教程 1】入门及配置
当你开始学习 Flask 时,了解如何进行基本的配置是非常重要的。Flask 是一个简单而灵活的 Python Web 框架,它允许你快速构建 Web 应用程序,并且易于学习。在这篇博客中,我将介绍如何从零开始进行 Flask 的基础配置,适合…...
石家庄河北银行的
有些时候河北石家庄这边的甲方客户人员就是太苛刻了,尤其是银行业 比如河北银行的信息部的卢斌,兰州人,这个人的人品极度恶劣,对乙方的外包人员特别苛刻,像个大爷一样。自己什么都不会,连sql 都不会写&…...

【CCNP ENCOR OCG】CHAPTER 2》Spanning Tree Protocol
目录 “Do I Know This Already?” Quiz Foundation Topics Spanning Tree Protocol Fundamentals 802.1D Port States Spanning Tree Path Cost Root Bridge Election Locating Root Ports Locating Blocked Designated Switch Ports Verification of VLANs on Trun…...
docker无法映射/挂载根目录
docker无法映射(挂载)根目录下的文件夹只能映射家目录 最近想要使用nas-tools做做刮削,电影存在一个机械磁盘里,机械磁盘被挂载到/data1下,发现一个很奇怪的问题,docker只能挂载成功home目录下的文件夹&am…...
C++中不要重新定义继承而来的non-virtual函数
在 C 中,重定义继承而来的 non-virtual(非虚)函数通常是不推荐的,原因如下: 隐藏父类的实现:如果在派生类中重定义了一个非虚函数,这将隐藏父类中具有相同名称和参数的函数。这意味着即使通过基…...

C++ 对象型参数和返回值
对象型参数和返回值 1.对象型类型作为函数的参数2.对象型参数作为函数的返回值 1.对象型类型作为函数的参数 使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象 例子: // 使用对象类型作为函数的参数 void test1(Car car) {}完整代…...
LeetCode 字符串专题——KMP算法_28. 找出字符串中第一个匹配项的下标
字符串专题——KMP算法 KMP算法例题 KMP算法 待更新 例题 https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/ class Solution {vector<int> next;void getNext(string s){int j-1;next[0]-1;int lens.size();for(int i…...

上班不想用脑子写代码了怎么办?那就试试Baidu Comate啊宝贝
本文目录 前言1、视频编程实战1.1、熟悉代码库中的代码1.2、参考现有代码编写新代码 2、下载使用教程3、使用体验3.1、AutoWork 产品测评3.2、解决有关ajax请求后重定向问题3.3、询问编程相关知识3.3.1、cookie和session的区别与联系3.3.2、数据库中主键外键的相关知识 4、问题…...

【管理咨询宝藏94】某国际咨询公司供应链财务数字化转型方案
本报告首发于公号“管理咨询宝藏”,如需阅读完整版报告内容,请查阅公号“管理咨询宝藏”。 【管理咨询宝藏94】某国际咨询公司供应链&财务数字化转型方案 【格式】PDF版本 【关键词】国际咨询公司、制造型企业转型、数字化转型 【核心观点】 - 172…...
C++_使用邻接表(链表-指针)实现有向图[完整示例及解释]
这个程序是一个图的实现,使用邻接表来表示图的结构: 1. 结构定义部分: - AdjListNode 结构定义了邻接表中的节点,每个节点包含一个名称和一个指向下一个邻接节点的指针。 - Node 结构定义了图中的节点,每个节点…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...