spin_lock & mutex_lock的区别? 【转】

转自:http://www.cnblogs.com/bitter-first-sweet-last/p/4528633.html

转自:http://blog.youkuaiyun.com/sunnytina/article/details/7615520

为什么需要内核锁?
多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是可以访问所有内核数据的,因此要对共享数据进行保护,即互斥处理
 
有哪些内核锁机制?
(1)原子操作
atomic_t数据类型,atomic_inc(atomic_t *v)将v加1
原子操作比普通操作效率要低,因此必要时才使用,且不能与普通操作混合使用
如果是单核处理器,则原子操作与普通操作相同
(2)自旋锁
spinlock_t数据类型,spin_lock(&lock)和spin_unlock(&lock)是加锁和解锁
等待解锁的进程将反复检查锁是否释放,而不会进入睡眠状态(忙等待),所以常用于短期保护某段代码
同时,持有自旋锁的进程也不允许睡眠,不然会造成死锁——因为睡眠可能造成持有锁的进程被重新调度,而再次申请自己已持有的锁
如果是单核处理器,则自旋锁定义为空操作,因为简单的关闭中断即可实现互斥
(3)信号量与互斥量
struct semaphore数据类型,down(struct semaphore * sem)和up(struct semaphore * sem)是占用和释放
struct mutex数据类型,mutex_lock(struct mutex *lock)和mutex_unlock(struct mutex *lock)是加锁和解锁
竞争信号量与互斥量时需要进行进程睡眠和唤醒,代价较高,所以不适于短期代码保护,适用于保护较长的临界区
 
互斥量与信号量的区别?(转载但找不到原文出处)
(1)互斥量用于线程的互斥,信号线用于线程的同步
这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
(2)互斥量值只能为0/1,信号量值可以为非负整数
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问
(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到
 
 
 
 
 
[转]mutex和spin lock的区别
mutex和spin lock的区别和应用(sleep-waiting和busy-waiting的区别)2011-10-19 11:43
信号量mutex是sleep-waiting。 就是说当没有获得mutex时,会有上下文切换,将自己、加到忙等待队列中,直到另外一个线程释放mutex并唤醒它,而这时CPU是空闲的,可以调度别的任务处理。

而自旋锁spin lock是busy-waiting。就是说当没有可用的锁时,就一直忙等待并不停的进行锁请求,直到得到这个锁为止。这个过程中cpu始终处于忙状态,不能做别的任务。

例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0 和Core1上。 用spin-lock,coer0上的线程就会始终占用CPU。
另外一个值得注意的细节是spin lock耗费了更多的user time。这就是因为两个线程分别运行在两个核上,大部分时间只有一个线程能拿到锁,所以另一个线程就一直在它运行的core上进行忙等待,CPU占用率一直是100%;而mutex则不同,当对锁的请求失败后上下文切换就会发生,这样就能空出一个核来进行别的运算任务了。(其实这种上下文切换对已经拿着锁的那个线程性能也是有影响的,因为当该线程释放该锁时它需要通知操作系统去唤醒那些被阻塞的线程,这也是额外的开销)

总结
(1)Mutex适合对锁操作非常频繁的场景,并且具有更好的适应性。尽管相比spin lock它会花费更多的开销(主要是上下文切换),但是它能适合实际开发中复杂的应用场景,在保证一定性能的前提下提供更大的灵活度。

(2)spin lock的lock/unlock性能更好(花费更少的cpu指令),但是它只适应用于临界区运行时间很短的场景。而在实际软件开发中,除非程序员对自己的程序的锁操作行为非常的了解,否则使用spin lock不是一个好主意(通常一个多线程程序中对锁的操作有数以万次,如果失败的锁操作(contended lock requests)过多的话就会浪费很多的时间进行空等待)。

(3)更保险的方法或许是先(保守的)使用 Mutex,然后如果对性能还有进一步的需求,可以尝试使用spin lock进行调优。毕竟我们的程序不像Linux kernel那样对性能需求那么高(Linux Kernel最常用的锁操作是spin lock和rw lock)。




本文由该问题引入到内核锁的讨论,归纳如下

为什么需要内核锁?
多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是可以访问所有内核数据的,因此要对共享数据进行保护,即互斥处理

有哪些内核锁机制?
(1)原子操作
atomic_t数据类型,atomic_inc(atomic_t *v)将v加1
原子操作比普通操作效率要低,因此必要时才使用,且不能与普通操作混合使用
如果是单核处理器,则原子操作与普通操作相同
(2)自旋锁
spinlock_t数据类型,spin_lock(&lock)和spin_unlock(&lock)是加锁和解锁
等待解锁的进程将反复检查锁是否释放,而不会进入睡眠状态(忙等待),所以常用于短期保护某段代码
同时,持有自旋锁的进程也不允许睡眠,不然会造成死锁——因为睡眠可能造成持有锁的进程被重新调度,而再次申请自己已持有的锁
如果是单核处理器,则自旋锁定义为空操作,因为简单的关闭中断即可实现互斥
(3)信号量与互斥量
struct semaphore数据类型,down(struct semaphore * sem)和up(struct semaphore * sem)是占用和释放
struct mutex数据类型,mutex_lock(struct mutex *lock)和mutex_unlock(struct mutex *lock)是加锁和解锁
竞争信号量与互斥量时需要进行进程睡眠和唤醒,代价较高,所以不适于短期代码保护,适用于保护较长的临界区
 
互斥量与信号量的区别?(转载但找不到原文出处)
(1)互斥量用于线程的互斥,信号线用于线程的同步
这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
(2)互斥量值只能为0/1,信号量值可以为非负整数
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问
(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到
【作者】 张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
<think>我们正在讨论 `pthread_spin_lock(&ep->lock);` 的相关信息。 根据用户提供的引用片段,我们可以看到这段代码出现在两个地方: 引用[1]:在从就绪队列中取出事件时使用了自旋锁 引用[2]:在向就绪队列中添加事件时使用了自旋锁 功能:`pthread_spin_lock` 是POSIX线程库提供的自旋锁加锁函数。在这里,它用来保护事件就绪队列(`ep->rdlist`)就绪事件计数(`ep->rdnum`)的并发访问。 使用方法: 1. 初始化:在初始化事件循环(eventpoll)结构时,需要先初始化自旋锁。通常使用`pthread_spin_init`。 2. 加锁:在临界区代码前调用`pthread_spin_lock`,确保只有一个线程可以进入临界区。 3. 解锁:在临界区代码后调用`pthread_spin_unlock`。 4. 销毁:在不再需要自旋锁时,使用`pthread_spin_destroy`销毁。 潜在问题: 1. 死锁:如果某个线程在持有自旋锁的情况下被阻塞(如调用了可能阻塞的函数),其他尝试获取该锁的线程将一直自旋,消耗CPU。 2. 性能问题:自旋锁适合临界区非常短的情况。如果临界区较长,自旋锁会浪费大量的CPU时间(因为等待的线程不会让出CPU,而是忙等)。 3. 优先级反:在实时系统中,低优先级线程持有锁,高优先级线程自旋等待,而低优先级线程无法运行,导致高优先级线程长时间等待。 4. 递归加锁:自旋锁不可递归。同一线程重复加锁同一个自旋锁会导致死锁。 结合引用中的代码,我们可以看到: 在引用[1]中,临界区操作包括从就绪队列中取出多个事件(循环操作),如果就绪事件很多,临界区可能较长,这时使用自旋锁可能不是最佳选择(因为其他线程会自旋等待)。 在引用[2]中,临界区操作较短(设置标志、插入链表、计数增加),使用自旋锁是合适的。 建议: 1. 确保临界区尽可能短。 2. 避免在自旋锁保护的临界区内调用可能阻塞的函数(如I/O操作、内存分配、互斥锁等)。 3. 在多核系统中使用自旋锁才有效(单核系统上自旋锁没有意义,因为自旋期间不可能有线程释放锁,因为持有锁的线程无法运行)。 另外,引用[2]中除了使用自旋锁,还使用了互斥锁条件变量(`pthread_mutex_lock``pthread_cond_signal`),这是因为不同的资源需要不同的同步机制: - 自旋锁保护的是就绪队列(rdlist)就绪计数(rdnum)的并发访问(这些操作非常快)。 - 互斥锁条件变量则用于等待/通知机制(epoll_wait的阻塞等待事件到来的唤醒)。 对比: 在引用[4]中提到的事件通知机制中,唤醒操作使用了条件变量,而条件变量的使用通常需要配合互斥锁(而不是自旋锁)。所以这里的设计是合理的:自旋锁只保护就绪队列,条件变量的保护使用单独的互斥锁(`ep->cdmtx`)。 因此,我们可以这样总结: ### `pthread_spin_lock(&ep->lock)` 详解 #### 一、功能作用 1. **同步机制**:保护事件就绪队列(`ep->rdlist`)就绪事件计数(`ep->rdnum`)的并发访问[^1][^2]。 2. **适用场景**:临界区操作非常短暂(如链表插入/删除、计数增减)且多核环境。 #### 二、使用步骤 ```c // 初始化(通常在创建eventpoll时) pthread_spin_init(&ep->lock, PTHREAD_PROCESS_PRIVATE); // 临界区加锁 pthread_spin_lock(&ep->lock); /* 操作共享资源 */ pthread_spin_unlock(&ep->lock); // 销毁(在销毁eventpoll时) pthread_spin_destroy(&ep->lock); ``` #### 三、性能对比 | 同步机制 | 适用场景 | 等待方式 | |----------------|----------------------------|-----------| | 自旋锁 | 极短临界区、多核CPU | 忙等待 | | 互斥锁 | 较长临界区、单核/多核 | 睡眠等待 | #### 四、潜在风险与规避 1. **死锁风险** - 禁止在自旋锁保护区内调用阻塞函数(如`read`, `malloc`, `pthread_mutex_lock`等) - 规避方案:临界区代码必须为**非阻塞原子操作** 2. **CPU资源浪费** - 长时间自旋将导致CPU空(表现为该CPU核心100%占用)[^3] - 规避方案:临界区操作控制在$\leq 100$个时钟周期内 3. **优先级反** - 实时系统中需配合优先级继承协议(如`pthread_spin_init`设置`PTHREAD_PROCESS_SHARED`属性) - 示例代码: ```c pthread_spinlock_t lock; pthread_spinattr_t attr; pthread_spinattr_init(&attr); pthread_spinattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); pthread_spinattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); // 优先级继承 pthread_spin_init(&lock, &attr); ``` #### 五、在epoll实现中的具体应用 1. **生产者侧**(事件回调)[^2] ```mermaid graph LR A[网络事件触发] --> B[回调nepoll_event_callback] B --> C{epitem存在?} C -->|是| D[获取自旋锁] D --> E[插入就绪队列] E --> F[解锁并发送条件信号] ``` 临界区仅包含: - 设置`epi->rdy=1` - 链表插入`LIST_INSERT_HEAD` - 计数递增`ep->rdnum++` 2. **消费者侧**(`epoll_wait`)[^1] ```c pthread_spin_lock(&ep->lock); while (!LIST_EMPTY(&ep->rdlist)) { // 从就绪队列取出事件 // 更新计数 } pthread_spin_unlock(&ep->lock); ``` 注意:此处使用`while`循环处理多个事件,但实际队列操作时间复杂度为$O(1)$,整体仍高效。 #### 六、调试与监控建议 1. **锁竞争检测** - 使用`perf lock`分析自旋锁等待时间: ```bash perf lock record -a ./your_program perf lock report ``` 2. **生产环境指标** | 监控项 | 预警阈值 | 工具 | |----------------------|---------------|-------------------| | 自旋锁平均等待时间 | >1微秒 | `perf`/`bpftrace` | | 就绪队列最大长度 | >1000事件 | 程序内置统计 | 3. **压力测试用例设计** ```c // 模拟高并发事件 for (int i=0; i<1000000; i++) { nepoll_event_callback(ep, rand()%maxfd, EPOLLIN); // 并发调用 } ``` §§相关问题§§ 1. 自旋锁互斥锁在Linux内核中的实现区别是什么? 2. 如何检测自旋锁导致的CPU空问题? 3. 在用户态epoll实现中,是否可以用互斥锁替代自旋锁?为什么? </think>### `pthread_spin_lock(&ep->lock)` 详解 #### 一、功能作用 1. **同步机制**:保护事件就绪队列(`ep->rdlist`)就绪事件计数(`ep->rdnum`)的并发访问[^1][^2] 2. **临界区保护**:确保在多线程环境下,对事件队列的操作是原子性的 3. **高性能设计**:相比互斥锁,自旋锁在短期锁定时性能更优(避免线程上下文切换) #### 二、使用步骤 ```c // 初始化(通常在创建eventpoll时) pthread_spin_init(&ep->lock, PTHREAD_PROCESS_PRIVATE); // 临界区加锁 pthread_spin_lock(&ep->lock); /* 操作共享资源 */ pthread_spin_unlock(&ep->lock); // 销毁(在销毁eventpoll时) pthread_spin_destroy(&ep->lock); ``` #### 三、在epoll实现中的具体应用 1. **生产者侧**(事件回调)[^2]: ```mermaid graph LR A[网络事件触发] --> B[回调nepoll_event_callback] B --> C{epitem存在?} C -->|是| D[获取自旋锁] D --> E[插入就绪队列] E --> F[解锁并发送条件信号] ``` 2. **消费者侧**(`epoll_wait`)[^1]: ```c pthread_spin_lock(&ep->lock); while (!LIST_EMPTY(&ep->rdlist)) { // 从就绪队列取出事件 // 更新计数 } pthread_spin_unlock(&ep->lock); ``` #### 四、性能对比 | 同步机制 | 适用场景 | 等待方式 | |----------------|----------------------------|-----------| | 自旋锁 | 极短临界区、多核CPU | 忙等待 | | 互斥锁 | 较长临界区、单核/多核 | 睡眠等待 | #### 五、潜在问题与解决方案 1. **死锁风险** - 禁止在锁内调用阻塞函数(如`read`, `malloc`等) - 规避方案:临界区代码必须为**非阻塞原子操作** 2. **CPU资源浪费** - 长时间自旋导致CPU空(表现为该CPU核心100%占用)[^3] - 规避方案:临界区操作控制在$\leq 100$个时钟周期内 3. **优先级反** - 实时系统中需配合优先级继承协议 - 示例代码: ```c pthread_spinattr_t attr; pthread_spinattr_init(&attr); pthread_spinattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); pthread_spin_init(&ep->lock, &attr); ``` #### 六、最佳实践建议 1. **临界区设计原则**: - 仅包含内存操作(无系统调用) - 操作复杂度$O(1)$(如链表插入/删除) - 避免函数调用(内联简单操作) 2. **调试监控指标**: ```c // 监控自旋锁等待时间 struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); pthread_spin_lock(&ep->lock); clock_gettime(CLOCK_MONOTONIC, &end); // 计算纳秒级耗时:(end.tv_sec - start.tv_sec)*1e9 + (end.tv_nsec - start.tv_nsec) ``` 3. **替代方案考量**: ```c if (pthread_spin_trylock(&ep->lock) == EBUSY) { // 锁竞争激烈时切换互斥锁 pthread_mutex_lock(&fallback_mutex); } ``` #### 七、在ET模式下的特殊考量 当使用EPOLLET(边缘触发)模式时[^3],自旋锁保护的就绪队列操作必须配合: ```c // ET模式必须循环读取直到EAGAIN pthread_spin_lock(&ep->lock); while (!LIST_EMPTY(&ep->rdlist)) { struct epitem *epi = LIST_FIRST(&ep->rdlist); // 处理事件... if (epi->event.events & EPOLLET) { read_all_available_data(epi->sockfd); // 确保读取所有数据 } } pthread_spin_unlock(&ep->lock); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值