哪些网站可以医生做兼职,莆田网站建设方法,网站建设项目,南通网站搭建定制一、并行编程
1、Parallel 类
Parallel类是System.Threading.Tasks命名空间中的一个重要类#xff0c;它提供数据并行和任务并行的高级抽象。
For和ForEach
Parallel类下的For和ForEach对应着普通的循环和遍历(普通的for和foreach)#xff0c;但执行时会尝试在多个线程上…一、并行编程
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
Liststring items new Liststring { 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%100select i;
并行度
在默认的情况下PLINQ会使用计算机上所有的处理器使用WithDegreeOfParallelism用于指定用于并行查询执行的处理器的最大数量即同时执行的任务数量。
int[] data Enumerable.Range(0, 100).ToArray();
var query from i in data.AsParallel().WithDegreeOfParallelism(3)where i%100select i;
排序
默认情况下PLINQ 不保证输出顺序与输入顺序一致。如果需要保持顺序可以使用AsOrdered()方法但是调用该方法时PLINQ 将尝试在并行处理的同时保留元素的顺序这也就意味着性能可能会下降。
int[] data Enumerable.Range(0, 100).ToArray();
var query from i in data.AsParallel().AsOrdered()where i%100select 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并发集合
BlockingCollectionT
提供了一种线程安全的方式来在多个生产者线程和消费者线程之间共享数据。当集合为空时尝试从中取数据的操作会被阻塞直到有数据可用。同样地当集合已满如果设置了容量限制时尝试添加数据的操作也会被阻塞。
using System.Collections.Concurrent;BlockingCollectionint collection new BlockingCollectionint();// 启动生产者任务
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); 常用类 ConcurrentBagT 这是一个无序的线程安全集合允许线程安全地添加和移除元素。 ConcurrentDictionaryTKey, TValue 这是一个线程安全的字典支持线程安全地添加、移除和访问键值对。 ConcurrentQueueT 这是一个线程安全的先进先出FIFO集合支持线程安全地入队和出队操作。 ConcurrentStackT 这是一个线程安全的后进先出LIFO集合支持线程安全地推入和弹出操作。 PartitionerTSource 用于将数据划分为多个分区以便并行处理。这通常与TPLTask Parallel Library一起使用以便在多个线程或任务上并行处理数据。 OrderablePartitionerTSource 这是一个特殊的分区器它保留了元素的顺序允许在并行处理的同时保持元素的顺序。 三、异步流
允许你以异步的方式处理数据流当你需要在不阻塞线程或其他重要线程的情况下处理大量数据时使用。关键字为IAsyncEnumerableT它允许你以异步的方式枚举数据序列而不需要一次性加载所有数据到内存中。
public class Account
{public async IAsyncEnumerableint GenerateAsyncStream(){for (int i 0; i 10; i){await Task.Delay(1000);yield return i;yield return i 1;}}
}
IAsyncEnumerableint asyncStream new Account().GenerateAsyncStream();await foreach (int i in asyncStream)
{Console.WriteLine(i);
}
常见的并发编程方法还有异步编程和多线程编程等。
四、并发的注意
使用并发编程的时候应该避免以下及其其他问题的出现通常并发编程的错误都是因为多个线程同时访问和修改共享资源或者由于线程之间的同步和协调不当导致的。
1、竞态条件
竞态条件发生在两个或多个线程同时访问共享资源并且它们的访问顺序会影响程序的结果在计算共享数据时由于多个线程计算顺序的不同导致最终计算的结果也不同导致的。
2、死锁
当两个或多个线程相互等待对方释放资源时导致所有线程都无法继续执行这通常是因为线程间的锁顺序不一致导致的。
3、活锁
当线程们都在忙于响应其他线程的动作但无法完成它们自己的任务时导致的。
4、饥饿
当其他线程一直占用资源而得不到释放时某些线程长时间得不到执行的机会而导致的。
5、数据不一致
当多个线程没有正确同步对共享数据的访问时可能会导致数据的不一致状态这可能是因为一个线程正在读取数据而另一个线程同时修改了这些数据。