Thread Local

本文探讨了在C# JobSystem中如何避免Random全局共享带来的竞态条件,通过使用ThreadLocal技术和线程专属随机数生成器,确保多线程环境下随机数的正确性和一致性。

Thread Local

这节让我们一起来思考一个游戏中比较常见的例子——随机数的使用。
这在以前是一个很简单的问题,因为Unity其实已经为我们准备好了响应的函数封装—— UnityEngine.Random ,我们只需要调用相应的API就可以了。
但是在多线程环境下这种情况发生了一些变化。其实从Unity文档中我们就可以总结出 UnityEngine.Random 有以下特点:
  1. 是一个class
  2. Static的单例
  3. 内部状态全局共享
根据以上特点我们不难总结出 UnityEngine.Random 并不适合在C# Job System中使用。
针对第一点Unity已经给出了相应的替代实现,那就是在 mathmatics 包中的 Random
但是第2、3点还需要我们自己编码来解决,这就引出了我们今天的主题——基于线程本地存储(Thread Local Storage,下称TLS)的随机数使用。
废话不多说,我们上代码:
    
struct RandomVelocityJob : IJobFor { [ ReadOnly ] public NativeArray < float > speeds ; [ ReadOnly ] public Random random ; public float deltaTime ; //output public NativeArray < float3 > positions ; public void Execute ( int i ) { positions [ i ] += random . NextFloat3Direction ( ) * speeds [ i ] * deltaTime ; } } var random = new Random ( 1234 ) ; var randomVelocityJob = new RandomVelocityJob { speeds = m_Speeds , random = random , positions = m_Positions , deltaTime = Time . deltaTime , } ;
在上一节Demo的基础上,我们希望每次position移动的方向是随机的,于是我们加入了一个新的Random变量在每次执行 Execute() 方法的时候去随机一个新的方向去做位置计算。代码非常简单明了,但是却隐藏着问题。
问题来自于 random 变量,虽然 NextFloat3Direction() 看上去人畜无害,但是他会改变 random 的内部状态,这就导致了所有的worker线程会共享并改变 random 的状态,使 random 处在竞争条件(race condition)的状态。
我们需要找到一种线程安全的方式来使用 random 变量。
首先想到的就是加锁,显然这是效率比较低的做法。还有另外一种比较经典的做法就是使用线程本地存储(TLS),让每个线程拥有一份自己独有的资源,这样就自然避免了竞争条件(race condition)的问题。
为了实现TLS,我们首先需要为每一个worker线程初始化一个random变量。这里我们使用 JobsUtility.MaxJobThreadCount 来获取worker的最大数量。代码如下:
    
m_Randoms = new NativeArray < Random > ( JobsUtility . MaxJobThreadCount , Allocator . Persistent ) ; for ( int i = 0 ; i < m_Randoms . Length ; i ++ ) { m_Randoms [ i ] = Random . CreateFromIndex ( ( uint ) i ) ; }
在Job中我们需要知道当前执行的线程ID,我们可以在Job中声明一个int类型的变量并添加 [NativeSetThreadIndex] 属性,在job执行的过程中Unity会帮我们自动注入这个ID。 这样我们就可以在 Execute() 方法中利用线程ID获取线程独有的资源了。
    
struct RandomVelocityJob : IJobFor { //input [ ReadOnly ] public NativeArray < float > speeds ; [ ReadOnly ] public NativeArray < Random > randoms ; public float deltaTime ; [ NativeSetThreadIndex ] private int m_ThreadIdx ; //output public NativeArray < float3 > positions ; public void Execute ( int i ) { positions [ i ] += randoms [ m_ThreadIdx ] . NextFloat3Direction ( ) * speeds [ i ] * deltaTime ; } }
经过简单的几步我们就实现了一个TLS。希望可以给大家带来一些启发。
【文章目录】
  1. Thread Local
【电动汽车充电站有序充电调度的分散式优化】基于蒙特卡诺和拉格朗日的电动汽车优化调度(分时电价调度)(Matlab代码实现)内容概要:本文介绍了基于蒙特卡洛和拉格朗日方法的电动汽车充电站有序充电调度优化方案,重点在于采用分散式优化策略应对分时电价机制下的充电需求管理。通过构建数学模型,结合不确定性因素如用户充电行为和电网负荷波动,利用蒙特卡洛模拟生成大量场景,并运用拉格朗日松弛法对复杂问题进行分解求解,从而实现全局最优或近似最优的充电调度计划。该方法有效降低了电网峰值负荷压力,提升了充电站运营效率与经济效益,同时兼顾用户充电便利性。 适合人群:具备一定电力系统、优化算法和Matlab编程基础的高校研究生、科研人员及从事智能电网、电动汽车相关领域的工程技术人员。 使用场景及目标:①应用于电动汽车充电站的日常运营管理,优化充电负荷分布;②服务于城市智能交通系统规划,提升电网与交通系统的协同水平;③作为学术研究案例,用于验证分散式优化算法在复杂能源系统中的有效性。 阅读建议:建议读者结合Matlab代码实现部分,深入理解蒙特卡洛模拟与拉格朗日松弛法的具体实施步骤,重点关注场景生成、约束处理与迭代收敛过程,以便在实际项目中灵活应用与改进。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值