第一次作业-加锁

定义:
锁机制是多线程编程中最常用的同步机制,用来对多线程间共享的临界区进行保护。

锁的类型、什么情况下会用到锁机制?
共4种:spinlock(自旋锁)、 mutex(互斥量)、 semaphore(信号量)、 critical section(临界区);
当需要对临界区进行保护事,我们会使用锁机制。

不同 锁的定义及区别:
1.读写锁(Read-Write lock)读写锁又称为共享独占锁(shared-exclusive lock)、多读单写锁(multiple-read/single-write lock)或者非互斥信号量(non-mutual exclusion semaphore)。读写锁允许多个线程同时进行读访问,但是在某一时刻却最多只能由一个线程执行写操作。对于多个线程需要同时读共享数据却并不一定进行写操作的应用来说,读写锁是一种高效的同步机制。对于较长的共享数据,只为其设置一个读写锁会导致较长的访问时间,最好将其划分为多个小段并设置多个读写锁以进行同步。

2.旋转锁(Spin Lock) 旋转锁是一种非阻塞锁,由某个线程独占。采用旋转锁时,等待线程并不静态地阻塞在同步点,而是必须“旋转”,不断尝试直到最终获得该锁。旋转锁多用于多处理器系统中。这是因为,如果在单核处理器中采用旋转锁,当一个线程正在“旋转”时,将没有执行资源可供另一释放锁的线程使用。旋转锁适合于任何锁持有时间少于将一个线程阻塞和唤醒所需时间的场合。线程控制的变更,包括线程上下文的切换和线程数据结构的更新,可能比旋转锁需要更多的指令周期。旋转锁的持有时间应该限制在线程上下文切换时间的50%到100%之间(Kleiman,1996年)。在线程调用其他子系统时,线程不应持有旋转锁。对旋转锁的不当使用可能会导致线程饿死,因此需谨慎使用这种锁机制。旋转锁导致的饿死问题可使用排队技术来解决,即每个等待线程按照先进先出的顺序或者队列结构在一个独立的局部标识上进行旋转。

3.互斥量(Mutex) 互斥量是实现最简单的锁类型,因此有一些教科书一般以互斥量为例对锁原语进行描述。互斥量的释放并不仅仅依赖于释放操作,还可以引入一个定时器属性。如果在释放操作执行前发生定时器超时,则互斥量也会释放代码块或共享存储区供其他线程访问。当有异常发生时,可使用try-finally语句来确保互斥量被释放。定时器状态或try-finally语句的使用可以避免产生死锁。

4.递归锁(Recursive Lock) 递归锁是指可以被当前持有该锁的线程重复获取,而不会导致该线程产生死锁的锁类型。对递归锁而言,只有在当前持有线程的获取锁操作都有一个释放操作与之对应时,其他线程才可以获取该锁。因此,在使用递归锁时,必须要用足够的释放锁操作来平衡获取锁操作,实现这一目标的最佳方式是在单入口单出口代码块的两头一一对应地使用获取、释放操作,做法和在普通锁中一样。递归锁在递归函数中最有用。但是,总的来说,递归锁比非递归锁速度要慢。需要注意的是:调用线程获得几次递归锁必须释放几次递归锁。

5.大内核锁:大内核锁是用来保护临界区资源,避免出现多个处理器上的进程同时访问同一区域的。但这把锁独特的地方是,它不象自旋锁或信号量一样可以创建许多实例或者叫对象,每个对象保护特定的临界区。事实上整个内核只有一把这样的锁,一旦一个进程获得大内核锁,进入了被它保护的临界区,不但该临界区被锁住,所有被它保护的其它临界区都将无法访问,直到该进程释放大内核锁。这看似不可思议:一个进程在一个处理器上操作一个全局的链表,怎么可能导致其它进程无法访问另一个全局数组呢?使用两个自旋锁,一个保护链表,另一个保护数组不就解决了吗?可是如果你使用大内核锁,效果就是这样的。

自旋锁与互斥锁的区别:自旋锁不会引起调用者睡眠,如果自旋锁已经被另的执行单元保持,调用者就一直循环在那里,看是否该自旋锁的保持者已经释放了锁,“自旋”一词而就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。

代码如何体现加锁:
互斥锁:实际上是先给count做自减操作,然后使用本身的自旋锁进入临界区操作。首先取得count的值,再将count置为-1,判断如果原来count的值为1,也即互斥锁可以获得,则直接获取,跳出。否则进入循环反复测试互斥锁的状态。在循环中,也是先取得互斥锁原来的状态,再将其置为-1,判断如果可以获取(等于1),则退出循环,否则设置当前进程的状态为不可中断状态,解锁自身的自旋锁,进入睡眠状态,待被在调度唤醒时,再获得自身的自旋锁,进入新一次的查询其自身状态(该互斥锁的状态)的循环。

信号量:信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

你可能感兴趣的:(Linux)