一台服务器能运行多少个线程,大致取决于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.InvokeParallel.ForParallel.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 线程与锁的更多相关文章

  1. JAVA语言规范-线程和锁章节之同步、等待和通知

    JAVA语言规范:线程和锁 1 同步 java编程语言提供了线程间通信的多种机制.这些方法中最基本的是同步化,此方法是使用监视器实现的.JAVA中每个对象与一个监视器相关联,一个线程可以加锁和解锁监视 ...

  2. GIL与线程互斥锁

    GIL 是解释器级别的锁,是限制只有一个原生线程运行,防止多个原生线程之间修改底层的共享数据.而线程互斥锁是防止多个线程同时修改python内存空间的共享数据.

  3. Java线程:锁

    一.锁的原理 Java中每个对象都有一个内置锁,当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行的代码类的当前实例(this实例)有关的锁.获得一个对象的锁也称为获取锁.锁 ...

  4. (三)juc高级特性——虚假唤醒 / Condition / 按序交替 / ReadWriteLock / 线程八锁

    8. 生产者消费者案例-虚假唤醒 参考下面生产者消费者案例: /* * 生产者和消费者案例 */ public class TestProductorAndConsumer { public stat ...

  5. Java 线程与锁

    Synchronization synchronized语法可以获取锁, 当其他线程持有锁的时候该线程想要获取锁将会进入等待状态, 直到没有其他线程持有该锁 显示使用 synchronized (lo ...

  6. Java线程与锁

    概要:线程的实现方法. 线程调度.线程状态及转换.线程安全(5种分类.3种实现方法.锁优化技术) 进程是OS进行资源分配的基本单位,线程是CPU调度的基本单位. 1.线程的实现方法 可参阅 我是一个进 ...

  7. java多线程 -- 线程八锁

    一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些 ...

  8. GUC-10 线程八锁

    /* * 题目:判断打印的 "one" or "two" ? * * 1. 两个普通同步方法,两个线程,标准打印, 打印? //one two * 2. 新增 ...

  9. JUC——线程同步锁(锁处理机制简介)

    锁处理机制简介 juc的开发框架解决的核心问题是并发访问和数据安全操作问题,当进行并发访问的时候如果对于锁的控制不当,就会造成死锁这样的阻塞问题. 为了解决这样的缺陷,juc里面重新针对于锁的概念进行 ...

  10. GIL线程全局锁 协程

    GIL线程全局锁 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.对于io密集型任务 ...

随机推荐

  1. Java读取excel文件(.xlsx/.xls)和.csv文件存入MySQL数据库

    1 package com.reliable.service; 2 3 import com.csvreader.CsvReader; 4 import com.reliable.bean.FileD ...

  2. 2 JavaScript的基础类型

    2 JavaScript的基础类型 JS虽然是一个脚本语言. 麻雀虽小, 五脏俱全. 在js中也是可以像其他编程语言一样. 声明变量, 条件判断, 流程控制等等. 我们先看一下JS中的数据类型 在js ...

  3. SQL 递归核心思想(递归思维)

    目前很缺递归思维,主要是算法代码写得少,本篇记录下以 PostgreSQL 代码举例(主要是非常喜欢这款性能小钢炮数据库). 树状查询不多说,很简单大家基本都会,主要讲 cte 代码递归实现不同需求. ...

  4. Unity中国、Cocos为OpenHarmony游戏生态插上腾飞的翅膀

      2023年是OpenHarmony游戏生态百花齐放的一年!为了扩展OpenHarmony游戏生态,OpenHarmony在基金会成立了游戏SIG小组,游戏SIG小组联合cocos,从cocos2d ...

  5. C++判断操作系统位数

    //判断当前系统是否为64位 BOOL Is64BitSystem() { #ifdef _WIN64 return true; #elif _WIN32 HMODULE hModule = Load ...

  6. C# 字符串操作指南:长度、连接、插值、特殊字符和实用方法

    字符串用于存储文本.一个字符串变量包含由双引号括起的字符集合 示例: // 创建一个string类型的变量并赋予一个值 string greeting = "Hello"; 如果需 ...

  7. 6. Eigenvalues and Eigenvectors

    Keys: What are Eigenvalues and Eigenvectors? How to find Eigenvalues and Eigenvectors? Applications ...

  8. C# sqlclient数据库事务BeginTransaction()详解

    重载 重载 BeginTransaction() 开始数据库事务. BeginTransaction(IsolationLevel) 以指定的隔离级别启动数据库事务. BeginTransaction ...

  9. .NET MAUI开源免费的UI工具包 - Uranium

    前言 一直有小伙伴在微信公众号后台留言让我分享一下.NET MAUI相关的UI框架,今天大姚分享一个.NET MAUI开源.免费的UI工具包:Uranium. Uranium介绍 Uranium是一个 ...

  10. 想学习eTS开发?教你开发一款IQ-EQ测试应用

    原文:https://mp.weixin.qq.com/s/eZgifjirAW58dFCa0W7kSQ,点击链接查看更多技术内容. 开发者Mack基于HarmonyOS的ArkUI框架开发的IQ- ...