.Net 线程与锁
一台服务器能运行多少个线程,大致取决于CPU的管理能力。CPU负责线程的创建、协调、切换、销毁、暂停、唤醒、运行等。
一个应用程序中,必须有一个进程维持应用程序的运行环境,一个进程可同时有多个线程协作处理应用逻辑。
同步:单线程,每一步都执行结束并返回结果,下一步处于等待,阻塞程序流
异步:多线程,不需要等待执行结束,可继续执行下一步,形成并行处理,无序的不可预测的执行顺序
前台线程:主线程退出后,子线程直至完成计算。
后台线程:主线程退出后,子线程也会停止退出。
作者:[Sol·wang] - 博客园,原文出处:https://www.cnblogs.com/Sol-wang/p/14793008.html
一、线程的应用
常见的线程应用方式
- new Thread
- ThreadPool.QueueUserWorkItem 后台线程
- Task.Run / Task.Factory.StartNewd
- Parallel
- await / async
ThreadPool 线程池
线程池线程是后台线程。 每个线程均使用默认的堆栈大小,以默认的优先级运行,并且位于多线程单元中。 一旦线程池中的线程完成任务,它将返回到等待线程队列中。 这时开始即可重用它。 通过这种重复使用,应用程序可以避免产生为每个任务创建新线程的开销。
每个进程只有一个线程池。由线程池统一管理每个线程的创建/分配/销毁。
// 设置可同时并行运行的线程数
ThreadPool.SetMinThreads
ThreadPool.SetMaxThreads
// 用线程池中的后台线程执行方法
ThreadPool.QueueUserWorkItem(new WaitCallback(方法名), 参数);
Task
Task所用到的线程同样来自于ThreadPool。所以通过ThreadPool线程数量的设置有助于Task的线程管理。
// Action:有参 (Action 与 Func 的区别:Action不能有返回值,Func必须有返回值)
Action<string> action = (string a) =>
{
Console.WriteLine($"有参Action\tparams {a}\tTId {Thread.CurrentThread.ManagedThreadId}");
};
// Create a task
Task t1 = new Task(action, "alpha");
t1.Start();
// Task.Factory
Task t2 = Task.Factory.StartNew(action, "beta");
t2.Start();
// Task 的返回值(等待返回结果)
Task<int> task = new Task<int>(() =>
{
//...
return 0;
});
task.Start();
int result = task.Result;
// 仅仅启动一个Task
Task.Run(() =>
{
//...
});
// 批量启动 Task
// 同时并行运行至少30个线程的方式
ThreadPool.SetMinThreads(30, 50);
ThreadPool.SetMaxThreads(50, 70);
List<Task> tks = new List<Task>();
for (int i = 0; i < 30; i++)
{
tks.Add(Task.Run(() =>
{
//...
}));
}
// 等待线程全部运行结束
Task.WaitAll(tks.ToArray());
// 取消线程运行
// 更多取消方式参考 https://learn.microsoft.com/zh-cn/dotnet/standard/threading/cancellation-in-managed-threads
CancellationTokenSource cts = new CancellationTokenSource();
Task.Run(() =>
{
if (Console.ReadKey(true).KeyChar.ToString().ToUpperInvariant() == "C")
{
// 取消
cts.Cancel();
}
});
Parallel
多线程并行处理的Parallel.Invoke
,Parallel.For
,Parallel.ForEach
;无法确保顺序处理。
// 示例:并行运行几个方法
Parallel.Invoke(方法1, 方法2, () => 方法3, () => 方法4)
// ParallelOptions 参数设定
// 先指定启用30个线程同时处理的设置
ParallelOptions paroptions = new ParallelOptions();
paroptions.MaxDegreeOfParallelism = 30;
// 指定数量的线程,并行执行200遍
List<int> datas = new List<int>();
Parallel.For(0, 200, paroptions, index =>
{
datas.add(index);
});
// 指定数量的线程,并行读取集合数据
Parallel.ForEach(datas, paroptions, (item) =>
{
Console.WriteLine(item);
});
await / async
创建后台线程并行处理,并取得处理结果。
// 执行 async 方法(后台子线程执行)
Task<string> _task_result_1 = t1.Func1();
// 主线程不等返回结果,继续往下执行
// 继续执行方法
string _result_2 = t1.Func2();
// 以上两个方法 Func1()、Func2() 并行执行
// 取 Func1 的运行结果
string _result_1 = await _task_result_1;
// 整合两个方法的运行结果
int _total_ = _result_1.length + _result_2.length;
async 定义方法有返回值时用Task<T> 。
用 await 调用 async 定义的方法时,多线程,阻塞式同步执行。
不用 await 调用 async 定义的方法时,多线程,非阻塞异步执行。
二、常用的线程锁
lock
通用的标准锁,封装自应用级锁Monitor类,需要有线程共享的锁标识,告知其它线程是否等待。
Monitor
程序级锁,于单个应用范围内,通过获取或释放用于标识资源 T 来授予对共享资源的相互独占访问权限。
// 给要操作的对象加把锁,排他锁
Monitor.Enter(T);
// 释放当前锁,允许其它线程使用了
Monitor.Exit(T);
SpinLock
快速的、低级别的简易锁。不同于标准锁,适合应用于简单的、非耗时的逻辑处理,此场景下更多时候性能优于标准锁。
如果应用场景并非足够简单或存在不确定性的可能,SpinLock 将比标准锁开销更大。
static SpinLock _spinlock = new SpinLock();
bool lockTaken = false;
try
{
// 加锁
_spinlock.Enter(ref lockTaken);
// ...
}
finally
{
// 释放
if (lockTaken) _spinlock.Exit(false);
}
Interlocked
更细化的锁,针对性的场景时用来代替lock
,包括非空、递增等针对场景的应用,性能优于lock
。
// 递增场景案例
// lock 方式
lock(lockObject)
{
myField++;
}
// Interlocked 的替代方式
System.Threading.Interlocked.Increment(myField);
Semaphore
进程间同步,跨应用限制可同时访问某一资源或资源池的线程数,允许同时共享的线程数
// 创建时,定义同时的默认线程数和最多线程数(起始线程数,最多线程数)
Semaphore sem = new Semaphore(10, 20);
// 上锁
sem.WaitOne();
// 释放锁(一次释放线程数)
sem.Release(3);
应用场景:假如多线程写入DB,当给DB类上锁时,可结合DB的max_connection值,设置允许同时访问的线程数。
Mutex
// 阻挡锁住当前块,其它线程处于等待
// 超时后返回false,不允许其它线程进入
Mutex.WaitOne(timeout);
// 释放当前,一个,允许其它线程进入
Mutex.ReleaseMutex();
三、线程安全
当多个线程访问同一个对象的属性或方法时,对这些调用进行同步处理是非常重要的。如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步或者在调用方进行任何其它的协调操作,调用这个对象的行为都可以获得有效的结果,其成员不受中断影响的类,那这个对象是线程安全的。否则,一个线程可能会中断另一个线程正在执行的任务,可能使该对象处于无效状态。
通常多个线程在调用同一共享资源,为了解决线程间的同步和互相干扰中断,会对共享资源加锁,使其线程间有序的独占运行。
在早期的.NET版本中,提供了常用的线程安全类,通过Synchronized
方法创建的实例实现线程同步。
如:ArrayList,Hashtable
// Synchronized 方法案例
// 线程安全创建对象,并实现多线程同步
Hashtable ht = Hashtable.Synchronized(new Hashtable());
// 多线程调用 不用锁
ht.Add("key1", true);
线程安全的高性能集合类
.NET Framework 4 引入了几种专为支持多线程添加和删除操作而设计的集合类型。 为了实现线程安全,这些类型使用多种高效的锁定和免锁定同步机制。 同步会增加操作的开销。 开销数取决于所用的同步类型、执行的操作类型和其他因素,例如尝试并行访问该集合的线程数。
在 System.Collections.Concurrent
命名空间,其中包含多个线程安全且可缩放的集合类。 多个线程可以安全高效地从这些集合添加或删除项,而无需在用户代码中进行其他同步。 编写新代码时,只要将多个线程同时写入到集合时,就使用并发集合类。
直接在其它线程中对并发集合操作,不需要手动加锁:
ConcurrentBag
无序元素集合的线程安全实现
ConcurrentStack
LIFO(后进先出)堆栈的线程安全实现
ConcurrentQueue
FIFO(先进先出)队列的线程安全实现
ConcurrentDictionary
键值对字典的线程安全实现
.Net 线程与锁的更多相关文章
- JAVA语言规范-线程和锁章节之同步、等待和通知
JAVA语言规范:线程和锁 1 同步 java编程语言提供了线程间通信的多种机制.这些方法中最基本的是同步化,此方法是使用监视器实现的.JAVA中每个对象与一个监视器相关联,一个线程可以加锁和解锁监视 ...
- GIL与线程互斥锁
GIL 是解释器级别的锁,是限制只有一个原生线程运行,防止多个原生线程之间修改底层的共享数据.而线程互斥锁是防止多个线程同时修改python内存空间的共享数据.
- Java线程:锁
一.锁的原理 Java中每个对象都有一个内置锁,当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行的代码类的当前实例(this实例)有关的锁.获得一个对象的锁也称为获取锁.锁 ...
- (三)juc高级特性——虚假唤醒 / Condition / 按序交替 / ReadWriteLock / 线程八锁
8. 生产者消费者案例-虚假唤醒 参考下面生产者消费者案例: /* * 生产者和消费者案例 */ public class TestProductorAndConsumer { public stat ...
- Java 线程与锁
Synchronization synchronized语法可以获取锁, 当其他线程持有锁的时候该线程想要获取锁将会进入等待状态, 直到没有其他线程持有该锁 显示使用 synchronized (lo ...
- Java线程与锁
概要:线程的实现方法. 线程调度.线程状态及转换.线程安全(5种分类.3种实现方法.锁优化技术) 进程是OS进行资源分配的基本单位,线程是CPU调度的基本单位. 1.线程的实现方法 可参阅 我是一个进 ...
- java多线程 -- 线程八锁
一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些 ...
- GUC-10 线程八锁
/* * 题目:判断打印的 "one" or "two" ? * * 1. 两个普通同步方法,两个线程,标准打印, 打印? //one two * 2. 新增 ...
- JUC——线程同步锁(锁处理机制简介)
锁处理机制简介 juc的开发框架解决的核心问题是并发访问和数据安全操作问题,当进行并发访问的时候如果对于锁的控制不当,就会造成死锁这样的阻塞问题. 为了解决这样的缺陷,juc里面重新针对于锁的概念进行 ...
- GIL线程全局锁 协程
GIL线程全局锁 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.对于io密集型任务 ...
随机推荐
- Java读取excel文件(.xlsx/.xls)和.csv文件存入MySQL数据库
1 package com.reliable.service; 2 3 import com.csvreader.CsvReader; 4 import com.reliable.bean.FileD ...
- 2 JavaScript的基础类型
2 JavaScript的基础类型 JS虽然是一个脚本语言. 麻雀虽小, 五脏俱全. 在js中也是可以像其他编程语言一样. 声明变量, 条件判断, 流程控制等等. 我们先看一下JS中的数据类型 在js ...
- SQL 递归核心思想(递归思维)
目前很缺递归思维,主要是算法代码写得少,本篇记录下以 PostgreSQL 代码举例(主要是非常喜欢这款性能小钢炮数据库). 树状查询不多说,很简单大家基本都会,主要讲 cte 代码递归实现不同需求. ...
- Unity中国、Cocos为OpenHarmony游戏生态插上腾飞的翅膀
2023年是OpenHarmony游戏生态百花齐放的一年!为了扩展OpenHarmony游戏生态,OpenHarmony在基金会成立了游戏SIG小组,游戏SIG小组联合cocos,从cocos2d ...
- C++判断操作系统位数
//判断当前系统是否为64位 BOOL Is64BitSystem() { #ifdef _WIN64 return true; #elif _WIN32 HMODULE hModule = Load ...
- C# 字符串操作指南:长度、连接、插值、特殊字符和实用方法
字符串用于存储文本.一个字符串变量包含由双引号括起的字符集合 示例: // 创建一个string类型的变量并赋予一个值 string greeting = "Hello"; 如果需 ...
- 6. Eigenvalues and Eigenvectors
Keys: What are Eigenvalues and Eigenvectors? How to find Eigenvalues and Eigenvectors? Applications ...
- C# sqlclient数据库事务BeginTransaction()详解
重载 重载 BeginTransaction() 开始数据库事务. BeginTransaction(IsolationLevel) 以指定的隔离级别启动数据库事务. BeginTransaction ...
- .NET MAUI开源免费的UI工具包 - Uranium
前言 一直有小伙伴在微信公众号后台留言让我分享一下.NET MAUI相关的UI框架,今天大姚分享一个.NET MAUI开源.免费的UI工具包:Uranium. Uranium介绍 Uranium是一个 ...
- 想学习eTS开发?教你开发一款IQ-EQ测试应用
原文:https://mp.weixin.qq.com/s/eZgifjirAW58dFCa0W7kSQ,点击链接查看更多技术内容. 开发者Mack基于HarmonyOS的ArkUI框架开发的IQ- ...