为什么锁要求C#中的实例? [英] Why Do Locks Require Instances In C#?

查看:35
本文介绍了为什么锁要求C#中的实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为每个锁定对象使用对象实例的目的是什么?CLR是否存储由线程在调用 Monitor.Enter(instance)时传递的对象的实例,以便当另一个线程尝试输入锁时,CLR将检查由线程提供的实例.新线程,如果该实例与第一个线程实例匹配,那么CLR将新线程添加到第一个在先服务队列中,依此类推?

What is the purpose of using an object instance for each lock object? Is the CLR storing an instance of an object passed by a thread on a call to Monitor.Enter(instance) so that when another thread tries to enter a lock, the CLR will check the instance provided by the new thread and if the instance matches to the first threads instance then the CLR will add the new thread to a first in first served queue and so on?

推荐答案

CLR是否存储线程上的线程传递的对象的实例调用Monitor.Enter(instance),以便在另一个线程尝试执行以下操作时:输入锁,CLR将检查新提供的实例线程,如果该实例与第一个线程实例匹配,则CLR会将新线程添加到先进先出队列中,等等吗?

Is the CLR storing an instance of an object passed by a thread on a call to Monitor.Enter(instance) so that when another thread tries to enter a lock, the CLR will check the instance provided by the new thread and if the instance matches to the first threads instance then the CLR will add the new thread to a first in first served queue and so on?

不考虑指令重新排序和抖动所造成的其他魔术.

首先,让我们解决问题的重要部分:

Firstly, let's address the significant part of the question:

为什么锁需要C#中的实例?

Why Do Locks Require Instances In C#?

答案不是那么令人满意,但是归结为……好吧,必须采取某种方式!

The answer is not that satisfying, yet it boils down to… Well, it had to be done some way!

您可以想象 C#规范 CLR 可以使用魔术字符串 number 为了跟踪线程同步,设计人员选择使用引用类型.引用类型已经具有用于其他CLR活动的 header ,因此与其为您提供魔术数字字符串,保持在表格中,他们选择了引用类型双重使用标头来跟踪线程同步.故事结束了.

You could imagine that the C# specs and the CLR could use a magic string, or a number to keep track of thread synchronisation, yet the designers chose to use Reference Types. Reference types already have a header used for other CLR activity, so instead of giving you magic numbers or strings which keeps in a table, they opted for a dual use header for a reference type to keep track of thread synchronization. End of story basically.

Monitor 锁定对象必须为引用类型.值类型没有像引用类型这样的标头,部分原因是它们不需要定型并且不能由GC固定.此外,值类型可以被装箱,这基本上意味着它们被包裹在对象中.当您将值类型传递给 Monitor 时,它们会被装箱 ,而当您传递相同 值类型时将它们装箱到另一个对象中(该对象将取消 lock 的所有内部CLR管道).

Monitor lock objects need to be reference types. Value Types do not have headers like Reference Types, in part because they do not need finalization and can’t be pinned by the GC. Moreover, Value Types can be boxed, which basically means they get wrapped into an object. When you pass a value type to Monitor they get boxed, when you pass the same value type they get boxed into a different object (which negates all the internal CLR plumbing of a lock).

这主要是为什么值类型无法用于锁定的原因...

This is primarily the reason why value types can’t be use for locking...

让我们前进

值类型引用类型都有内部存储器布局.但是,引用类型还包含一个32位的 header ,以帮助CLR在 object 上执行某些管家任务(如上所述).这就是我们要谈论的

Both value types and reference types have an internal memory layout. However, reference types also contain a 32 bit header to help the CLR perform certain housekeeping tasks on an object (as discussed above). This is what we will be talking about

标题中有很多内容,但是离火箭科学还很远.不过,关于锁定,这里只有2个概念很重要,即标头 Lock State 信息或标头是否需要扩展到 Sync Block Table .

There is a fair bit that goes on in the header, however it's far from rocket science. Though, regarding locking, there are only 2 concepts that matter here, the header Lock State information or whether the header needed to be inflated to the Sync Block Table.

对象标题

典型对象标头格式中的最高有效字节如下所示.

The most significant bytes in a typical object header format is shown below.

|31            0|    
----------------| 
|7|6|5|4|3|2| --|
 | | | | | |
 | | | | | +- BIT_SBLK_IS_HASHCODE : set if the rest of the word is a hash code (or sync block index)
 | | | | +--- BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX : set if hashcode or sync block index is set
 | | | +----- BIT_SBLK_SPIN_LOCK : lock the header for exclusive mutation on spin
 | | +------- BIT_SBLK_GC_RESERVE : set if the object is pinned
 | +--------- BIT_SBLK_FINALIZER_RUN : set if finalized already
 +----------- BIT_SBLK_AGILE_IN_PROGRESS : set if locking on AppDomain agile classes

标头负责保存CLR的某些易于访问的信息,主要是 GC 的一小部分数据,是否生成了HashCode和 Lock State 对象的em>.但是,由于对象标头(32位)的大小有限,因此可能需要将标头膨胀同步块表.通常会在以下情况下完成此操作.

The header is responsible for holding certain easily accessible information for the CLR, primarily this is tiny bits of data for the GC, whether a HashCode has been generated and the Lock State of the object. Though, because there is only limited size in an object header (32 bits) the header may need to be inflated to the Sync Block Table. This would typically be done in the following situation.

  • 已生成哈希码并已获取精简锁.
  • 已获取胖锁
  • 涉及到条件变量(通过Wait,Pulse等)

标题不够大.

锁定状态

对象上创建后, CLR 将查看标头并首先确定是否需要查找任何锁同步块表中的信息,只需查看设置的位即可.如果没有 Thin Lock ,它将创建一个(如果适用).如果有一个 Thin Lock ,它将尝试旋转并等待它.如果标头已膨胀,它将在同步块表"中查找锁定信息(待续...).

Once you create a lock on an object, the CLR will look at the header and first determine whether it needs to find any locking information in the Sync Block table, it does this simply by looking at the bits that are set. If there is no Thin Lock, it will create one (if applicable). If there is a Thin Lock it will try to spin and wait for it. If the header has been inflated, it will look in the Sync Block Table for locking information (to be continued...).

锁定有2种不同的口味.关键区域条件变量.

Locking comes in 2 different flavours. Critical regions, and conditional variables.

  • 关键区域 Enter Exit Lock
  • 的结果
  • 条件变量 Wait Pulse 等的结果,这是另一个故事,因为它与问题无关.
  • Critical regions are the result of Enter, Exit, Lock etc
  • Conditional variables are the result of Wait, Pulse etc, this is another story as it's not related to the question.

关于关键区域,CLR可以为它们锁定两种主要方法. Thin Lock Fat Lock .CLR在混合锁模型中同时使用了这两种方法,这基本上意味着它首先尝试一个,然后又退回到下一个.

Regarding Critical Regions, there are 2 primary ways the CLR can lock for them. Thin Lock, and Fat Lock. The CLR uses the both of these in a hybrid lock model, which basically means it tries one first then falls back to the next.

瘦锁

对象薄锁头

|31       |26                |15                   |9         0|
----------------------------------------------------------------
|7|6|5|4|3| App Domain Index | Lock Recusion Level | Thread id |
 | | | | | 
 | | | | | 
 | | | | +--- BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX = 0 can store a thin lock

一个 Thin Lock 基本上由一个 App域索引递归级别和一个托管线程ID 组成..线程ID 由锁线程自动设置,如果为零,或者为非零,则使用简单的旋转等待来多次重读锁状态以获取锁.如果一段时间后仍无法使用该锁,则需要升级该锁(如果尚未这样做),请将 Thin Lock 扩展到 Sync Block Table em>和 true *锁将需要根据内核事件(如自动重置事件)在操作中注册.

A Thin Lock basically consists of an App Domain Index, Recursion Level, and a Managed Thread Id. The Thread Id is atomically set by a locking thread if zero, or if nonzero, a simple spin wait is used to reread the lock state various times to acquire the lock. If after a period of time the lock is still not available, it will need to promote the lock (and if not already done so), inflate the Thin Lock to the Sync Block Table and a true* lock will need to be registered with the operating based on a Kernel Event (like an auto reset event).

Thin Lock 的确切发音,它是一种较轻的重量机构,并且运行速度很快,但是它却需要旋转核以完成其工作.这种 hybrid lock 机制在短发行版场景中更快,效率更低,但是对于更长的争用场景,CLR会退回到资源占用较少,内核锁定较慢的状态.简而言之,总体而言,日常使用通常会获得更好的效果.

A Thin Lock is exactly how it sounds, it is a lighter weight mechanism and fast, however it’s at the cost of spinning the core to achieve its work. This hybrid locking mechanism is faster and less efficient for short release scenarios, however the CLR falls back to less resource intensive slower kernel lock for longer contention scenarios. In short, overall it will typically garner better results for day to day usage.

脂肪锁定

如果发生争用或涉及到条件变量(通过Wait,Pulse等),则需要将其他信息存储在 Sync Block 中,例如作为内核对象的句柄或与锁关联的事件列表.胖锁的确像是它的样子,它是一种更具攻击性的锁,它速度较慢,但​​由于它不会围绕CPU不必要地旋转而减少了资源占用,因此更适合较长的锁定周期.

In cases where contention occurs or when a condition variable is involved (via Wait, Pulse etc.), additional info needs to be stored in the Sync Block, such as a handle to the kernel object or a list of events that are associated with the lock. A fat lock is exactly what it sounds like, it’s a more aggressive lock, it's slower yet it's less resource-intensive as it doesn’t revolve around the CPU spinning needlessly, it's more suited to a longer lock cycle.

同步阻止表

对象同步块索引标头

|31         |25               0|
--------------------------------
|7|6|5|4|3|2| Sync Block Index |
 | | | | | |
 | | | | | +- BIT_SBLK_IS_HASHCODE = 0 sync block index
 | | | | +--- BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX = 1 hash code or sync block index

CLR在堆上具有一个预初始化,可回收,缓存和可重用的同步块表.该表可能包含哈希码(从标头迁移),以及对象标头同步块索引(发生升级/膨胀时)所引用的各种类型的锁定信息.

The CLR has a pre-initialized, recyclable, cached and reusable Sync Block Table on the heap. This table may contain a Hash code (migrated from the header), and various types of locking information that is referenced to by the Objects Header Sync Block Index (when promoted / inflation occurs).

将它们放在一起 *

调用 Monitor.Enter 时,CLR通过将当前线程ID(除其他事项外)存储在对象标头中(如所讨论的那样)或将其提升为来注册采集.Sycnc块表.如果存在 Thin Lock ,则CLR将通过检查标头或 Sync Block Table 短暂地使用自旋来等待该锁解除竞争.

When Monitor.Enter is called, the CLR registers the acquisition either by storing the current thread Id (among other things) in the object header (as discussed) or promoting it to the Sycnc Block Table. If there is a Thin Lock the CLR will briefly use a spin to wait for the lock to be uncontended either by checking the header or the Sync Block Table.

如果自旋锁在一定量的自旋后仍无法获得锁,则它最终可能需要在操作系统中注册自动重置事件并将句柄存储在 Sync Block中表格.此时,一个等待线程将只在该句柄上等待.

If the spin lock cannot gain the lock after a certain amount of spins, it may eventually need to register an auto reset event with the operating system and store the handle in the Sync Block Table. At this point a waiting thread will just wait on that handle.

然后CLR将新线程添加到先入先服务队列中,依此类推?

then the CLR will add the new thread to a first in first served queue and so on?

不,没有这样的队列,随后所有这些都可能导致不公平的行为.线程具有窃取信号和唤醒之间的锁的能力,但是CLR确实以有序的方式帮助它,并尝试防止[lock convoy] [3].

No, there is no queue as such, and subsequently this all can lead to unfair behaviour. Threads have the ability to steal the lock between the signal and the wakeup, however the CLR does help this in an orderly fashion and tries prevent a [lock convoy][3].

因此,显然在这里,锁的类型(关键区域和条件变量),CLR内存模型,回调的工作方式等等,还有很多其他东西.但这应该为您回答最初的问题提供一个起点

So, there is obviously a lot more that has been glossed over here with the types of locks (critical regions and condition variables), the CLR memory model, how the callbacks work and so forth. But it should give you a starting point to answer your initial question

免责声明:由于其中的许多信息都是CLR实现细节,因此实际上可能会发生更改.

这篇关于为什么锁要求C#中的实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆