本文目录
- spinlock自旋锁是如何实现的
- 为什么SpinLock的实现中应该加上PAUSE指令
- 进程进入等待状态有哪几种方式
- 在已经加锁的代码段中使用原子操作有哪些作用
- 如何:使用 SpinLock 进行低级别同步
- 旋转锁spinlock 为什么会和优先级有关系
spinlock自旋锁是如何实现的
在 x86 平台上,spinlock 主要通过处理器的 lock 指令前缀实现当某个线程的一条指令访问某个内存的时候,其他的线程的指令无法访问该内存的功能。因此在 spinlock 初始化阶段,将锁变量中的值某个值 k 赋为1。在加锁的时候,使用 lock decl (%eax) 指令互斥地将该变量变成0,并且将结果是否问0 赋值给 EFLAGS寄存器 的对应位。只有加上锁的线程才会结果才是0,其他线程的结果不是0。接着通过判断该对应位判断是否加上锁。如果没有加上,则循环执行 lock decl (%eax),直到加上为止。其中 %eax 是这个变量的地址。这里用的是gcc 的AT&T语法的汇编。
为什么SpinLock的实现中应该加上PAUSE指令
当spinlock执行lock()获得锁失败后会进行busy loop(参考这段代码),不断检测锁状态,尝试获得锁。这么做有一个缺陷:频繁的检测会让流水线上充满了读操作。另外一个线程往流水线上丢入一个锁变量写操作的时候,必须对流水线进行重排,因为CPU必须保证所有读操作读到正确的值。流水线重排十分耗时,影响lock()的性能。[cpp] view plain copyinline int rdlock() { int ret = common::OB_SUCCESS; int64_t tmp = 0; while (true) { tmp = ref_cnt_; if (0 》 tmp || 0 《 wait_write_) { // 写优先 continue; } else { int64_t nv = tmp + 1; if (tmp == (int64_t)atomic_compare_exchange((uint64_t*)&ref_cnt_, nv, tmp)) { break; } } } return ret; }; 为了解决这个问题,intel发明了pause指令。这个指令的本质功能:让加锁失败时cpu睡眠30个(about)clock,从而使得读操作的频率低很多。流水线重排的代价也会小很多。
进程进入等待状态有哪几种方式
进程进入等待状态有:
A、CPU调度给优先级更高的线程。
B、阻塞的线程获得资源或者信号。
C、在时间片轮转的情况下,如果时间片到了。
D、获得spinlock未果。
a、是由运行态进入就绪态。
b、是有阻塞太进入就绪态。
c、是由就绪态进入运行态。
d、一直就绪态。
内容
一个计算机系统进程包括(或者说“拥有”)下列数据:
那个程序的可运行机器码的一个在存储器的映像。 分配到的存储器(通常包括虚拟内存的一个区域)。存储器的内容包括可运行代码、特定于进程的数据(输入、输出)、调用堆栈、堆栈(用于保存运行时运数中途产生的数据)。 分配给该进程的资源的操作系统描述符,诸如文件描述符(Unix术语)或文件句柄(Windows)、数据源和数据终端。
在已经加锁的代码段中使用原子操作有哪些作用
这个很简单,因为第36行在访问的时候没有加锁而是使用了原子操作…所以43行在写入的时候必须使用原子操作。一个简单的原则:如果在任意位置对某个变量使用了原子操作,那么其他所有位置在使用该变量时,都要使用原子操作。
对于可能多cpu同时访问的数据读写都必须用原子操作,虽然仅仅是一条指令,但是在cpu内部也是分为很多步骤的,如果不用原子操作,就容易发生错乱。在读取操作的时候,会从cpu自己的缓存读取,而写入的时候,这个cpu会告诉其他cpu我要写这个地址了,把缓存丢弃。那如果同时发生读写怎么办呢,读取指令已经用了本地缓存,返回的是旧值。
考虑2个cpu a,b,ab都进行如下操作读取v并且写新值,v是原值0则新值设置1这个是一般的spinlock操作指令。如果不用原子操作,那么ab同时操作时候,ab读取到的旧值都是0,读取到0表示获取到锁了。2个cpu都同时拥有锁,冲突了。如果用原子操作,就会加上lock指令,这个指令锁住总线,就可以安全操作了。
像题主这个赋值语句,v=0,如果不用原子操作,另外的cpu在执行cmpxchg(0,1),即v等于1的时候设置v=0,假设原来v=1,那么能够操作成功,就把v设置成0,并且认为原值是1,这个时候v=0也一起操作。2者都同时写入0,看起来好像没啥问题。但是另外那个cpu看到原值是1,就一直等其他cpu把这个值释放,即执行v=0,而事实上是已经执行过了,他却还在那里一直等候。就像个人同时给对方打电话,听到都是忙音,都以为对方在忙。
如何:使用 SpinLock 进行低级别同步
相比较于标准锁来说,增加一点工作量即可提高 SpinLock 的性能。但需注意的一点是,SpinLock 比标准锁更耗费资源。您可以使用分析工具中并发分析功能,查看哪种类型的锁可以在您的程序中提供更好的性能。有关更多信息,请参见并发可视化工具。C#VBclass SpinLockDemo2 { constint N = 100000; static Queue《Data》 _queue = new Queue《Data》(); staticobject _lock = new Object(); static SpinLock _spinlock = new SpinLock(); class Data { publicstring Name { get; set; } publicdouble Number { get; set; } } staticvoid Main(string args) { // First use a standard lock for comparison purposes. UseLock(); _queue.Clear(); UseSpinLock(); Console.WriteLine(“Press a key“); Console.ReadKey(); } privatestaticvoid UpdateWithSpinLock(Data d, int i) { bool lockTaken = false; try { _spinlock.Enter(ref lockTaken); _queue.Enqueue( d ); } finally { if (lockTaken) _spinlock.Exit(false); } } privatestaticvoid UseSpinLock() { Stopwatch sw = Stopwatch.StartNew(); Parallel.Invoke( () =》 { for (int i = 0; i 《 N; i++) { UpdateWithSpinLock(new Data() { Name = i.ToString(), Number = i }, i); } }, () =》 { for (int i = 0; i 《 N; i++) { UpdateWithSpinLock(new Data() { Name = i.ToString(), Number = i }, i); } } ); sw.Stop(); Console.WriteLine(“elapsed ms with spinlock: {0}“, sw.ElapsedMilliseconds); } staticvoid UpdateWithLock(Data d, int i) { lock (_lock) { _queue.Enqueue(d); } } privatestaticvoid UseLock() { Stopwatch sw = Stopwatch.StartNew(); Parallel.Invoke( () =》 { for (int i = 0; i 《 N; i++) { UpdateWithLock(new Data() { Name = i.ToString(), Number = i }, i); } }, () =》 { for (int i = 0; i 《 N; i++) { UpdateWithLock(new Data() { Name = i.ToString(), Number = i }, i); } } ); sw.Stop(); Console.WriteLine(“elapsed ms with lock: {0}“, sw.ElapsedMilliseconds); } } SpinLock 对于很长时间都不会持有共享资源上的锁的情况可能很有用。对于此类情况,在多核计算机上,一种有效的做法是让已阻塞的线程旋转几个周期,直至锁被释放。通过旋转,线程将不会进入阻塞状态(这是一个占用大量 CPU 资源的过程)。在某些情况下,SpinLock 将会停止旋转,以防止出现逻辑处理器资源不足的现象,或出现系统上超线程的优先级反转的情况。此示例使用 System.Collections.Generic.Queue《T》 类,这要求针对多线程的访问进行用户同步。在以.NET Framework 4 为目标的应用程序中,还可以选择使用 System.Collections.Concurrent.ConcurrentQueue《T》,这不需要任何用户锁。请注意,在对 Exit 的调用中使用了 false(在 Visual Basic 中为 False)。这样可以提供最佳性能。
旋转锁spinlock 为什么会和优先级有关系
关闭抢占是在所有spin_lock中都会做的,下面阐述禁止抢占的原因:如果不禁止内核抢断(或者不禁止中断),可能会有以下的情况发生(假设进程B比进程A具有更高的优先级):进程A获得spinlock lock进程B运行(抢占进程A)进程B获取spinlock lock由于进程B比进程A优先级高,所以进程B在进程A之前运行,而进程B需要进程A释放lock之后才能运行,于是,死锁