自旋锁与信号量的区别
- 格式:doc
- 大小:31.00 KB
- 文档页数:4
linux内核调度与spinlock的相互关系
嵌入式linux中文站关于自旋锁用法介绍的文章,已经有很多,但有些细节的地方点的还不够透,因此我们在这里将着重介绍自旋锁相关的知识。
一、自旋锁(spinlock)简介
自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区中。
这点可以应用在多处理机器、或运行在单处理器上的抢占式内核中需要的锁定服务。
二、信号量简介
这里也介绍下信号量的概念,因为它的用法和自旋锁有相似的地方。
Linux中的信号量是一种睡眠锁。
如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。
这时处理器获得自由去执行其它代码。
当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
三、自旋锁和信号量对比
在很多地方自旋锁和信号量可以选择任何一个使用,但也有一些地方只能选择某一种。
下面对比一些两者的用法。
表1-1自旋锁和信号量对比
应用场合
信号量or自旋锁
低开销加锁(临界区执行时间较快)
优先选择自旋锁
低开销加锁(临界区执行时间较长)
优先选择信号量
临界区可能包含引起睡眠的代码
不能选自旋锁,可以选择信号量。
实现临界区互斥的方法临界区互斥是指在多线程环境下,同一时间只允许一个线程进入临界区执行,以保证共享资源的正确性和一致性。
下面是实现临界区互斥的几种方法。
1.锁机制锁机制是最常见和简单的实现临界区互斥的方法。
通过在进入临界区前获得锁,并在退出临界区时释放锁,可以保证同一时间只有一个线程能够进入临界区。
常见的锁包括互斥锁(Mutex)、自旋锁(Spinlock)、读写锁(ReadWriteLock)等。
2.信号量机制信号量机制是一种更加复杂的实现临界区互斥的方法。
通过定义一个信号量来表示资源的可用数量,每次进入临界区前需要尝试获取信号量,如果信号量为0则等待,直到资源可用为止。
在离开临界区时,释放信号量,使得其他线程可以获得资源。
3.条件变量条件变量是一种用于线程间通信的机制,通常与锁结合使用来实现临界区互斥。
在进入临界区之前,线程首先需要获得一个互斥锁,然后判断条件变量是否满足要求,如果条件不满足则等待,直到条件满足后再进入临界区。
在离开临界区时,线程释放互斥锁,并通知其他线程条件变量已经满足。
4.原子操作原子操作是一种不可分割的操作,能够保证在多线程环境下执行时不会被中断。
原子操作常用于实现临界区互斥的情况下,通过在进入临界区前进行原子操作,可以确保同一时间只有一个线程能够进入临界区。
常见的原子操作包括原子加减、原子比较交换等。
5.读写锁读写锁是一种特殊的锁机制,用于保证在读多写少的情况下提高并发性能。
读写锁区分读线程和写线程,允许多个读线程同时进入临界区,但只允许一个写线程进入临界区。
通过读写锁可以充分利用多线程环境下的并发性,提高程序的执行效率。
总结起来,实现临界区互斥的方法有锁机制、信号量机制、条件变量、原子操作以及读写锁等。
不同的方法适用于不同的场景,开发人员应根据实际需求选择最适合的方法来实现临界区互斥。
同时,正确使用这些方法也需要考虑性能、可靠性和易用性等方面的因素。
linux cpu核访问同一片内存的保护机制在多核处理器系统中,多个 CPU 核心可以同时访问同一片内存。
为了确保在并发访问中数据的一致性,Linux 使用了一些机制来保护共享内存区域,防止并发访问导致数据不一致或错误。
以下是 Linux 中 CPU 核访问同一片内存的保护机制:1. 原子操作:• Linux 提供了一系列原子操作,确保在一个原子操作中对共享内存的访问是不可中断的。
例如,atomic_t 类型和相关的原子操作函数可以确保某些操作是原子的,不会被中断。
2. 自旋锁(Spin Lock):•自旋锁是一种在多核系统中实现互斥的手段。
当一个核心获得了自旋锁,其他核心如果需要访问被保护的共享内存,就需要等待。
它们会不断尝试获取锁,而不是进入睡眠状态,因此称为“自旋”。
3. 信号量:•信号量是一种更高级的同步机制,可以用于实现对共享资源的访问控制。
Linux 提供了 semaphore 相关的 API,允许程序员使用信号量来保护共享内存。
4. 读写锁(Read-Write Lock):•读写锁允许多个核心同时读取共享内存,但在写入时必须互斥。
这种机制在对于读访问比写访问频繁的场景中可以提高性能。
5. 屏障(Memory Barriers):•内存屏障用于强制 CPU 在执行指令时遵循一定的内存访问顺序。
这对于确保在多核系统中,不同核心看到的内存访问顺序是一致的,从而保证数据的一致性。
6. 写时复制(Copy-On-Write):•对于一些共享的数据结构,Linux 可以使用写时复制技术。
当一个核心需要修改数据时,它会复制一份私有副本,而不是直接修改共享数据。
这样可以避免多核心同时写入导致的冲突。
这些机制的选择取决于应用的需求和性能特性。
在编写多线程或多进程应用程序时,程序员需要根据实际情况选择合适的同步机制来确保数据的一致性和正确性。
同步函数知识点总结一、同步函数的基本概念1. 同步函数是指在调用函数时,需要等待函数执行完成后才能继续执行后续的代码。
相对应的,异步函数是指在调用函数后,可以继续执行后续的代码,无需等待函数执行完成。
2. 同步函数通常用于多线程或并发编程中,可以确保多个线程之间的数据同步和一致性。
3. 在同步函数中,通常会使用锁、信号量等机制来保证同一时间只有一个线程可以访问共享资源,以避免并发访问导致的数据竞争和不一致性。
二、同步函数的实现方式1. 锁机制:最常用的同步函数实现方式是通过锁来保护共享资源,当一个线程获得锁后,其他线程需要等待锁释放才能访问共享资源。
2. 信号量机制:信号量是一种计数器,用来控制对共享资源的访问。
通过信号量的计数值来控制同时访问共享资源的线程数。
3. 条件变量:条件变量是一种线程间通信的机制,可用于线程的等待和通知。
当共享资源不满足某个条件时,线程可以等待条件变量的通知,当共享资源满足条件时,通知等待的线程可以继续执行。
三、同步函数的使用场景1. 多线程编程:在多线程编程中,同步函数是非常重要的,可以确保多个线程之间的数据同步和一致性。
2. 并发编程:在并发编程中,同步函数也非常重要,可以避免并发访问导致的数据竞争和不一致性。
3. 数据库操作:在数据库操作中,同步函数可以确保对数据库的操作是一致的和安全的。
四、同步函数的优缺点1. 优点:(1)确保数据的同步和一致性:同步函数可以确保多个线程之间的数据同步和一致性,避免数据竞争和不一致性。
(2)保护共享资源:同步函数可以保护共享资源,避免并发访问导致的数据竞争和不一致性。
(3)提高程序的安全性:同步函数可以提高程序的安全性,避免并发访问导致的数据异常。
2. 缺点:(1)性能影响:同步函数会带来一定的性能开销,因为需要对共享资源进行加锁操作,可能导致部分线程需要等待锁的释放。
(2)死锁风险:如果同步函数的实现不当,可能会导致死锁的发生,造成程序无法继续执行。
信号量一.什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。
信号量的值为正的时候,说明它空闲。
所测试的线程可以锁定而使用它。
若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
二.信号量的分类在学习信号量之前,我们必须先知道——Linux提供两种信号量:POSIX信号量又分为有名信号量和无名信号量。
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。
无名信号量,其值保存在内存中。
倘若对信号量没有以上的全面认识的话,你就会很快发现自己在信号量的森林里迷失了方向。
三.内核信号量1.内核信号量的构成内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。
然而,当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。
只有在资源被释放时,进程才再次变为可运行。
只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。
count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。
wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。
sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。
2.内核信号量中的等待队列(删除,没有联系)上面已经提到了内核信号量使用了等待队列wait_queue来实现阻塞操作。
当某任务由于没有某种条件没有得到满足时,它就被挂到等待队列中睡眠。
当条件得到满足时,该任务就被移出等待队列,此时并不意味着该任务就被马上执行,因为它又被移进工作队列中等待CPU资源,在适当的时机被调度。
内核信号量是在内部使用等待队列的,也就是说该等待队列对用户是隐藏的,无须用户干涉。
由用户真正使用的等待队列我们将在另外的篇章进行详解。
3.内核信号量的相关函数(2)申请内核信号量所保护的资源:4.内核信号量的使用例程在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。
c 面试题多线程多线程面试题多线程是计算机科学中一个重要的概念,它指的是在一个程序中同时执行多个线程。
这种并发性的设计可以提高程序的效率和响应能力。
在进行多线程的面试中,通常会涉及一些重要的问题和概念。
本文将针对多线程面试题进行讨论和解答。
一、什么是多线程?多线程是指在一个程序中同时执行多个线程的技术。
每个线程可以独立地执行不同的任务,而不会相互干扰。
多线程可以提高程序的效率和响应能力,特别是在需要进行复杂计算或者处理大量数据的情况下。
二、线程和进程的区别是什么?线程是进程中的一个执行单元,一个进程可以包含多个线程。
线程共享进程的资源,如内存空间和文件句柄。
进程是操作系统分配资源的基本单位,每个进程都有自己独立的内存空间和执行权。
三、请描述线程同步和线程互斥的概念。
线程同步是指多个线程之间的协调和合作,以确保它们能够正确地访问共享数据。
线程互斥是指使得同一时间只有一个线程访问某个共享资源,其他线程必须等待。
常用的线程同步和互斥的机制有锁、信号量、条件变量等。
四、请解释以下概念:互斥锁、读写锁、自旋锁和信号量。
互斥锁(Mutex)是一种用于控制多个线程对共享资源进行互斥访问的机制。
只有获得互斥锁的线程才能进行访问,其他线程必须等待。
读写锁(ReadWrite Lock)是一种用于控制读写操作的机制。
多个线程可以同时进行读操作,但只有一个线程可以进行写操作,且在写操作期间禁止读操作。
自旋锁(Spinlock)是一种基于忙等待的锁机制。
它不会让线程进入睡眠状态,而是通过循环不断检查锁状态,直到获取到锁。
信号量(Semaphore)是一种允许多个线程同时访问某个资源的机制。
它可以用来控制同时访问某个资源的线程数量。
五、什么是死锁?如何避免死锁?死锁是指两个或多个线程之间相互等待对方释放资源而无法继续执行的情况。
避免死锁的方法包括:避免使用多个锁、确保加锁的顺序一致、尽量减少锁的持有时间、使用死锁检测和解除机制等。
zephyr 临界段在Zephyr实时操作系统(RTOS)中,临界段(critical section)是指一段不能被中断的代码区域,即在这段代码执行期间,系统不会响应任何外部的中断请求。
这样做是为了保护共享资源,防止并发访问导致的数据不一致或其他不可预测的行为。
在Zephyr中,可以使用多种机制来实现临界段的保护,包括:互斥体(Mutexes):互斥体是一种常用的同步原语,用于保护共享资源。
当一个任务(thread)获得互斥体时,其他试图获得该互斥体的任务将被阻塞,直到当前任务释放互斥体。
信号量(Semaphores):虽然信号量主要用于任务间的同步,但在某些情况下,它们也可以用于实现临界段的保护。
通过控制信号量的计数,可以限制对共享资源的访问。
自旋锁(Spinlocks):自旋锁是一种低开销的同步机制,适用于短时间内的临界段保护。
当一个任务试图获得一个已被其他任务持有的自旋锁时,它将持续在一个循环中检查锁的状态,直到锁被释放。
原子操作(Atomic Operations):原子操作是不可中断的指令,它们在执行过程中不会被其他任务或中断打断。
Zephyr提供了一组原子操作API,用于执行基本的原子读-改-写操作。
中断禁用(Disabling Interrupts):这是最直接的保护临界段的方法。
通过禁用中断,可以确保当前任务在执行临界段代码时不会被打断。
但是,这种方法应该谨慎使用,因为它会影响系统的实时性。
在Zephyr中,通常推荐使用互斥体或信号量来保护临界段,因为它们提供了更灵活和安全的同步机制。
原子操作和自旋锁适用于非常短时间的临界段保护,而禁用中断应该作为最后的手段,仅在必要时使用。
请注意,具体的实现细节和API可能会根据Zephyr的版本和配置有所不同。
建议查阅最新的Zephyr文档以获取准确的信息。
多个互斥方案概述在软件开发过程中,我们经常会遇到需要实现互斥功能的情况。
互斥功能可以确保同时只有一个线程或进程能够访问共享资源,并避免竞态条件和数据不一致。
本文将介绍多个互斥方案,包括互斥锁、读写锁、自旋锁和信号量。
我们将讨论每种方案的特点、适用场景和相应的实现方式。
互斥锁互斥锁是最常见的互斥方案之一。
它通过在临界区代码前后设置锁来确保同一时间只有一个线程能够执行临界区代码。
特点•互斥锁是一种二进制的锁,只有两个状态:锁定和非锁定。
•锁的状态由线程负责维护,一个线程可以尝试获取锁,如果锁是空闲的,则获取成功并将锁状态置为锁定;如果锁已经被其他线程占用,则获取失败,线程会等待直到锁被释放。
•互斥锁通常在多线程环境下使用。
•互斥锁不支持递归。
适用场景互斥锁适用于以下情况:•临界区代码的执行时间相对较长。
•只需要互斥访问的资源是共享的。
•不需要支持递归。
实现方式在 C++ 中,可以使用 std::mutex 类来实现互斥锁的功能。
以下是一段示例代码:#include <mutex>std::mutex mtx;void CriticalSection(){std::lock_guard<std::mutex> lock(mtx);// 临界区代码}读写锁读写锁是一种特殊的互斥锁,它允许多个读操作同时进行,但写操作必须互斥执行。
读写锁可以提高读操作的并发性能。
特点•读写锁有三种状态:读锁定、写锁定和未锁定。
•多个线程可以同时获得读锁,但只能有一个线程获得写锁。
•写锁的获取是独占的,即其他线程无法同时获得读锁或写锁。
适用场景读写锁适用于以下情况:•读操作频繁且不会修改共享资源。
•写操作较少,但需要互斥执行,以保证数据一致性。
实现方式在 C++ 中,可以使用 std::shared_mutex 类来实现读写锁的功能。
以下是一段示例代码:#include <shared_mutex>std::shared_mutex rwMutex;void ReadOperation(){std::shared_lock<std::shared_mutex> lock(rwMutex);// 读操作}void WriteOperation(){std::unique_lock<std::shared_mutex> lock(rwMutex);// 写操作}自旋锁自旋锁是一种忙等待的互斥方案,线程会一直尝试获取锁,直到成功。
多线程同步与互斥方法
多线程同步和互斥是为了保证多个线程能够安全地访问共享资源而采取的措施。
下面是几种常见的多线程同步与互斥的方法:
1. 锁(lock):通过加锁的方式来保护临界区,只有获得锁的线程才能进入临界区执行代码,其他线程需要等待锁的释放。
常见的锁包括互斥锁(mutex)和读写锁(read-write lock)。
2. 信号量(semaphore):允许多个线程同时访问某个资源,但要限制同时访问的线程数量,通过信号量进行计数来实现。
3. 条件变量(condition variable):允许线程在某个条件满足时等待,直到其他线程发出信号通知它们继续执行。
4. 互斥量(mutex):一种特殊的锁,用于确保某段代码只能被一个线程执行,其他线程需要等待。
5. 读写锁(read-write lock):允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
6. 自旋锁(spin lock):不会引起线程的阻塞,在尝试获取锁时,会一直处于循环中直到获取到锁为止。
7. 可重入锁(reentrant lock):允许同一线程多次获取同一个锁而不会发生死锁。
以上方法都是为了解决多线程之间的冲突和竞争条件问题,保证线程安全和数据一致性。
根据具体的场景和需求,选择适合的同步与互斥方法可以提高多线程程序的性能和正确性。
c语言锁的类型和概念C语言中的锁是一种同步机制,用于控制多个线程或进程对共享资源的访问。
锁可以确保在任何时候只有一个线程或进程可以访问共享资源,以避免数据竞争和其他并发问题。
C语言中有几种不同类型的锁,每种锁都有其自己的特点和用途。
下面将介绍这些不同类型的锁及其概念。
1. 互斥锁互斥锁是最常见的一种锁类型,也是最简单和最基本的一种。
互斥锁可以确保在任何时候只有一个线程可以访问共享资源。
当一个线程获得了互斥锁时,其他线程必须等待该线程释放该锁后才能访问共享资源。
在C语言中,使用pthread_mutex_t结构体来表示互斥锁。
通过pthread_mutex_init函数初始化互斥锁,并使用pthread_mutex_lock和pthread_mutex_unlock函数来获取和释放该锁。
2. 读写锁读写锁是另一种常见的锁类型,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
这对于读取频繁但写入较少的应用程序非常有用。
在C语言中,使用pthread_rwlock_t结构体来表示读写锁。
通过pthread_rwlock_init函数初始化读写锁,并使用pthread_rwlock_rdlock和pthread_rwlock_wrlock函数来获得读取和写入锁,使用pthread_rwlock_unlock函数来释放锁。
3. 条件变量条件变量是一种允许线程等待特定条件的同步机制。
当一个线程需要等待某个条件满足时,它可以通过条件变量进入休眠状态,直到另一个线程发出信号通知该条件已经满足。
在C语言中,使用pthread_cond_t结构体来表示条件变量。
通过pthread_cond_init函数初始化条件变量,并使用pthread_cond_wait和pthread_cond_signal函数来等待和发出信号。
4. 自旋锁自旋锁是一种基于忙等待的锁类型,它允许线程在没有被阻塞的情况下等待共享资源。
自旋锁与信号量的区别
2012-07-12 13:56:57| 分类:笔试/C | 标签:信号量自旋锁区别|字号大中小订阅
信号量和自旋锁区别
自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。
而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。
------------------------------------------------------
虽然听起来两者之间的使用条件复杂,其实在实际使用中信号量和自旋锁并不易混淆。
注意以下原则:
如果代码需要睡眠——这往往是发生在和用户空间同步时——使用信号量是唯一的选择。
由于不受睡眠的限制,使用信号量通常来说更加简单一些。
如果需要在自旋锁和信号量中作选择,应该取决于锁被持有的时间长短。
理想情况是所有的锁都应该尽可能短的被持有,但是如果锁的持有时间较长的话,使用信号量是更好的选择。
另外,信号量不同于自旋锁,它不会关闭内核抢占,所以持有信号量的代码可以被抢占。
这意味者信号量不会对影响调度反应时间带来负面影响。
自旋锁对信号量
------------------------------------------------------
需求建议的加锁方法
低开销加锁优先使用自旋锁
短期锁定优先使用自旋锁
中断上下文中加锁使用自旋锁
长期加锁优先使用信号量
持有锁是需要睡眠、调度使用信号量————————————————————————————————
自旋锁
------------------------------------------------------
自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁)。
自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。
要是锁未被争用,请求它的内核任务便能立刻得到它并且继续进行。
自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。
自旋锁的基本形式如下:
spin_lock(&mr_lock);
//临界区
spin_unlock(&mr_lock);
因为自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区中。
这点很好地满足了对称多处理机器需要的锁定服务。
在单处理器上,自旋锁仅仅当作一个设置内核抢占的开关。
如果内核抢占也不存在,那么自旋锁会在编译时被完全剔除出内核。
简单的说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。
另外自旋锁不允许任务睡眠( 持有自旋锁的任务睡眠会造成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再次申请自己已持有的锁),它能够在中断上下文中使用。
死锁:假设有一个或多个内核任务和一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。
这便会发生所有内核任务都在相互等待,但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便意味着死锁发生了。
自死琐是说自己占有了某个资源,然后自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。
一、为什么用自旋锁
操作系统锁机制的基本原理,就是在某个锁操作过程中不能与其他锁操作交织执行,以免多个执行路径对内核中某些重要的数据及数据结构进行同时操作而造成混乱。
在不同的系统环境中,根据系统特点和操作需要,锁机制可以用多种方式来实现。
以Linux为例,其系统内核的锁机制一般通过3 种基本方式来实现,即原语、关中断和总线锁。
在单CPU系统中,CPU 的读—修改—写原语可以保证是原子的,即执行过程过中不会被中断,所以CPU 通过关中断的方式,从芯片级保证该操作所存取的数据不能被多个内核控制路径同时访问,避免交叉执行。
然而,在对称多处理器(SMP) 环境中,单CPU 涉及读—修改—写原语不再是原子的,因为,在某个CPU 执行读—修改—写指令时有多次总线操作,其他CPU 竞争总线,可导致对同一存储单元的读—写操作与其他CPU 对这一存储单元交叉,这时我们就需要用一个称为自旋锁(spin lock)的原始对象为CPU 提供锁定总线的方法。
二、自旋锁是什么
自旋锁(spin lock)是一个典型的对临界资源的互斥手段,它的名称来源于它的特性。
为了获得一个自旋锁,在某CPU上运行的代码需先执行一个原子操作,该操作测试并设置(test-and-set)某个内存变量,由于它是原子操作,所以在该操作完成之前其它CPU不可能访问这个内存变量。
如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行。
如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置(test-and-set)”操作,即开始“自旋”。
最后,锁的所有者通过重置该变量释放这个自旋锁,于是,某个等待的test-and-set操作向其调用者报告锁已释放。
三、关于自旋锁的几个事实
自旋锁实际上是忙等锁,当锁不可用时,CPU一直循环执行“测试并设置(test-and-set)”该锁直到可用而取得该锁,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。
这说明只有在占用锁的时间极短的情况下,使用自旋锁是合理的,因为此时某个CPU可能正在等待这个自旋锁。
当临界区较为短小时,如只是为了保证对数据修改的原子性,常用自旋锁;当临界区很大,或有共享设备的时候,需要较长时间占用锁,使用自旋锁就不是一个很好的选择,会降低CPU的效率。
自旋锁也存在死锁(deadlock)问题。
引发这个问题最常见的情况是要求递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU想第二次获得这个自旋锁,则该CPU将死锁。
自旋锁没有与其关联的“使用计数器”或“所有者标识”;锁或者被占用或者空闲。
如果你在锁被占用时获取它,你将等待到该锁被释放。
如果碰巧你的CPU已经拥有了该锁,那么用于释放锁的代码将得不到运行,因为你使CPU永远处于“测试并设置”某个内存变量的自旋状态。
另外,如果进程获得自旋锁之后再阻塞,也有可能导致死锁的发生。
由于自旋锁造成的死锁,会使整个系统挂起,影响非常大。
自旋锁一定是由系统内核调用的。
不可能在用户程序中由用户请求自旋锁。
当一个用户进程拥有自旋锁期间,内核是把代码提升到管态的级别上运行。
在内部,内核能获取自旋锁,但任何用户都做不到这一点
四、自旋锁与信号量比较
自旋锁和信号量是解决互斥问题的基本手段,无论是单处理系统还是多处理系统,它们可以不需修改代码地进行移植。
那么,这两个手段应该如何选择呢?这就要考虑临界区的性质和系统处理的要求。
从严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现有赖于后者。
信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。
如果竞争不上,会有上下文切换,进程可以去睡眠,但CPU不会停,会接着运行其他的执行路径。
从概念上说,这与单CPU或多CPU没有直接的关系,只是在信号量本身的实现上,为了保证信号量结构存取的原子性,在多CPU中需要自旋锁来互斥。
但是值得注意的是上下文切换需要一定时间,并且会使高速缓冲失效,对系统性能影响是很大的。
因此,只有当进程占用资源很长时间时,用信号量才是不错的选择。
当所要保护的临界区比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。
但是CPU得不到自旋锁会在那里空转直到锁成功为止,所以要求锁不能在临界区里停留很长时间,否则会降低系统的效率。
综上,自旋锁是一种保护数据结构或代码片段的原始方式,主要用于SMP中,用于CPU 同步,在某个时刻只允许一个进程访问临界区内的代码。
它的实现是基于CPU锁定数据总线的指令。
为保证系统效率,自旋锁锁定的临界区一般比较短。
在单CPU系统中,使用自旋锁的意义不大,还容易因为递归调用自旋锁造成死锁。
信号量
------------------------------------------------------
Linux中的信号量是一种睡眠锁。
如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。
这时处理器获得自由去执行其它代码。
当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。
信号量基本使用形式为:
static DECLARE_MUTEX(mr_sem);//声明互斥信号量
if(down_interruptible(&mr_sem))
//可被中断的睡眠,当信号来到,睡眠的任务被唤醒
//临界区
up(&mr_sem);。