了解Linux的锁与同步

上周看了Linux的进程与线程,对操做系统的底层有了更进一步的一些了解。我同时用Linux内核设计与实现Solaris内核结构两本书对比着看,这样更容易产生对比和引起思考。现代操做系统不少思路都是相同的,好比抢占式的多线程及内核、虚拟内存管理等方面。但另一方面仍是有不少差别。在了解锁和同步以前,原子操做是全部一切底层实现的基础。html

原子操做Atomic

一般操做系统和硬件都提供特性,能够对一个字节进行原子操做的的读写,而且一般在此基础上来实现更高级的锁特性。java

  • atomic_t结构

原子操做一般针对int或bit类型的数据,可是Linux并不能直接对int进行原子操做,而只能经过atomic_t的数据结构来进行。目前了解到的缘由有两个。数据结构

一是在老的Linux版本,atomic_t实际只有24位长,低8位用来作锁,以下图所示。这是因为Linux是一个跨平台的实现,能够运行在多种 CPU上,有些类型的CPU好比SPARC并无原生的atomic指令支持,因此只能在32位int使用8位来作同步锁,避免多个线程同时访问。(最新版SPARC实现已经突破此限制)多线程

 

另一个缘由是避免atomic_t传递到程序其余地方进行操做修改等。强制使用atomic_t,则避免被不恰当的误用。curl

atomic_t my_counter = ATOMIC_INIT(0);
val = atomic_read( &my_counter );
atomic_add( 1, &my_counter );
atomic_inc( &my_counter );
atomic_sub( 1, &my_counter );
atomic_dec( &my_counter );
  • 原子操做硬件上的实现

Solaris的实现是基于test-and-set的指令,而且该指令为原子操做。好比Solaris的实如今SPARC上是基于ldstub和cas指令,在x86上用的是cmpxchg指令。可是Linux彷佛直接用的add指令。jvm

OpenSolaris i386的实现ide

	movl	4(%esp), %edx	/ %edx = target address
	movl	(%edx), %eax	/ %eax = old value
1:
	leal	1(%eax), %ecx	/ %ecx = new value
	lock
	cmpxchgl %ecx, (%edx)	/ try to stick it in
	jne	1b
	movl	%ecx, %eax	/ return new value
	ret

在Linux源代码asm_i386/atomic.h中性能

/**  * atomic_add - add integer to atomic variable  * @i: integer value to add  * @v: pointer of type atomic_t  *  * Atomically adds @i to @v. Note that the guaranteed useful range  * of an atomic_t is only 24 bits.  */
static __inline__ void atomic_add(int i, atomic_t *v)
{
        __asm__ __volatile__(
                LOCK "addl %1,%0"
                :"=m" (v->counter)
                :"ir" (i), "m" (v->counter));
}
锁的类型
  • Spinlocks自旋锁

若是锁被占用,尝试获取锁的线程进入busy-wait状态,即CPU不停的循环检查锁是否可用。自旋锁适合占用锁很是短的场合,避免等待锁的线程sleep而带来的CPU两个context switch的开销。学习

  • Semaphores信号量

若是锁被占用,尝试获取锁的线程进入sleep状态,CPU切换到别的线程。当锁释放以后,系统会自动唤醒sleep的线程。信号量适合对锁占用较长时间的场合。ui

  • Adaptive locks自适应锁

顾名思义,自适应锁就是上面两种的结合。当线程尝试申请锁,会自动根据拥有锁的线程繁忙或sleep来选择是busy wait仍是sleep。这种锁只有Solaris内核提供,Linux上未见有相关描述。

几种锁的性能比较(Windows操做系统下,第一种相似atomic_inc, 2,3相似spinlock, 4,5相似semaphore)

 

(图片来源:Intel Software Network)

Java synchronization

再理论联系实际一下,看Java中的锁底层如何实现的。这篇关于JVM的Thin Lock, Fat Lock, SPIN Lock与Tasuki Lock中讲到Java synchronization实际上也是一种自适应锁。

因而,JVM早期版本的作法是,若是T1, T2,T3,T4…产生线程竞争,则T1经过CAS得到锁(此时是Thin Lock方式),若是T1在CAS期间得到锁,则T2,T3进入SPIN状态直到T1释放锁;而第二个得到锁的线程,好比T2,会将锁升级(Inflation)为Fat Lock,因而,之后尝试得到锁的线程都使用Mutex方式得到锁。

Java AtomicInteger的实现,彷佛和Solaris的实现很是相似,也是一个busy wait的方式

   /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        // unsafe.compareAndSwapInt是用本地代码实现
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
Solaris

感受OpenSolaris在不少地方要比Linux优秀,Solaris在理论设计和实践上都很是优雅,而Linux内核不少地方彷佛更偏工程实践方向一些。另外Solaris用来作学习操做系统更合适,它的mdb几乎无所不能。

我在VirtualBox虚拟机上安装了OpenSolaris,很是容易安装,使用这个Minimal OpenSolaris Appliance OVF p_w_picpath for VirtualBox 2.2 简易方法,安装一个没有gui的版本,大约3分钟之内就能够装好。OpenSolaris安装软件和Ubuntu同样方便,使用 pkg install SUNWxxx 的命令,好比 pkg install SUNWcurl

相关文章
相关标签/搜索