1 定义互斥锁(英语:Mutual exclusion,缩写 Mutex)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全域变量)进行读写的机制。 该目的通过将代码切片成一个一个的**临界区域(critical section)**达成。临界区域指的是一块对公共资源进行存取的代码,并非一种机制或是算法。一个程序、进程、线程可以拥有多个临界区域,但是并不一定会应用互斥锁。 例如:一段代码(甲)正在分步修改一块数据。这时,另一条线程(乙)由于一些原因被唤醒。如果乙此时去读取甲正在修改的数据,而甲碰巧还没有完成整个修改过程,这个时候这块数据的状态就处在极大的不确定状态中,读取到的数据当然也是有问题的。更严重的情况是乙也往这块地方写数据,这样的一来,后果将变得不可收拾。因此,多个线程间共享的数据必须被保护。达到这个目的的方法,就是确保同一时间只有一个临界区域处于运行状态,而其他的临界区域,无论是读是写,都必须被挂起并且不能获得运行机会。 互斥锁实现多线程同步的核心思想是:有线程访问进程空间中的公共资源时,该线程执行“加锁”操作(将资源“锁”起来),阻止其它线程访问。访问完成后,该线程负责完成“解锁”操作,将资源让给其它线程。当有多个线程想访问资源时,谁最先完成“加锁”操作,谁就最先访问资源。 当有多个线程想访问“加锁”状态下的公共资源时,它们只能等待资源“解锁”,所有线程会排成一个等待(阻塞)队列。资源解锁后,操作系统会唤醒等待队列中的所有线程,第一个访问资源的线程会率先将资源“锁”起来,其它线程则继续等待。当有多个线程想访问“加锁”状态下的公共资源时,它们只能等待资源“解锁”,所有线程会排成一个等待(阻塞)队列。资源解锁后,操作系统会唤醒等待队列中的所有线程,第一个访问资源的线程会率先将资源“锁”起来,其它线程则继续等待。 ![]() mutex有什么缺点?不同于mutex最初的设计与目的,现在的struct mutex是内核中最大的锁之一,比如在x86-64上,它差不多有32bytes的大小,而struct samaphore是24bytes,rw_semaphore为40bytes,更大的数据结构意味着占用更多的CPU缓存和更多的内存占用。 什么时候应该使用mutex?除非mutex的严格语义要求不合适或者临界区域阻止锁的共享,否则相较于其他锁原语来说更倾向于使用mutex 更多linux内核视频教程文档资料免费领取后台私信【内核】自行获取. ![]() ![]() mutex与spinlock的区别?![]() spinlock是让一个尝试获取它的线程在一个循环中等待的锁,线程在等待时会一直查看锁的状态。而mutex是一个可以让多个进程轮流分享相同资源的机制 spinlock就像是坐在车后座的熊孩子,一直问”到了吗?到了吗?到了吗?…“ mutex就像一个司机返回的信号,说”我们到了!“ 2 实现看一下Linux kernel-5.8是如何实现mutex的2 实现 struct mutex { atomic_long_t owner; spinlock_t wait_lock;#ifdef CONFIG_MUTEX_SPIN_ON_OWNER struct optimistic_spin_queue osq; /* Spinner MCS lock */#endif struct list_head wait_list;#ifdef CONFIG_DEBUG_MUTEXES void *magic;#endif#ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map;#endif}; 可以看到,mutex使用了原子变量owner来追踪锁的状态,owner实际上是指向当前mutex锁拥有者的struct task_struct *指针,所以当锁没有被持有时,owner为NULL。
上锁当要获取mutex时,通常有三种路径方式 fastpath:通过 cmpxchg() 当前任务与所有者来尝试原子性的获取锁。 这仅适用于无竞争的情况(cmpxchg() 检查 0UL,因此上面的所有 3 个状态位都必须为 0)。 如果锁被争用,它会转到下一个可能的路径。 具体代码调用链很长… /*不可中断的获取锁*/void __sched mutex_lock(struct mutex *lock){ might_sleep(); /*fastpath*/ if (!__mutex_trylock_fast(lock)) /*midpath and slowpath*/ __mutex_lock_slowpath(lock);}__mutex_trylock_fast(lock) -> atomic_long_try_cmpxchg_acquire(&lock->owner, &zero, curr) -> atomic64_try_cmpxchg_acquire(v, (s64 *)old, new);__mutex_lock_slowpath(lock)->__mutex_lock(lock, TASK_UNINTERRUPTIBLE, 0, NULL, _RET_IP_) -> __mutex_lock_common(lock, state, subclass, nest_lock, ip, NULL, false) /*可中断的获取锁*/int mutex_lock_interruptible(struct mutex *lock); 尝试上锁
释放锁void __sched mutex_unlock(struct mutex *lock){#ifndef CONFIG_DEBUG_LOCK_ALLOC if (__mutex_unlock_fast(lock)) return;#endif __mutex_unlock_slowpath(lock, _RET_IP_);} 跟加锁对称,也有fastpath, midpath, slowpath三条路径。 判断锁状态
很显而易见,mutex持有者不为NULL即表示锁定状态。 3 实际案例实验: #include <pthread.h>#include <stdio.h>#define LOOP 1000000int cnt = 0;int cs1 = 0, cs2 = 0;void* task(void* args) { while(1) { if(cnt >= LOOP) { break; } cnt++; if((int)args == 1) cs1 ++; else cs2++; } return NULL;}int main() { pthread_t tid1; pthread_t tid2; /* create the thread */ pthread_create(&tid1, NULL, task, (void*)1); pthread_create(&tid2, NULL, task, (void*)2); /* wait for thread to exit */ pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf('cnt = %d cs1=%d cs2=%d total=%d\n', cnt,cs1,cs2,cs1+cs2); return 0;} 输出:
正确结果不应该是1000000吗?为什么会出错呢,我们可以从汇编角度来分析一下。 $> g++ -E test.c -o test.i$> g++ -S test.i -o test.s$> vim test.s .file 'test.c' .globl _cnt .bss .align 4_cnt: .space 4 .text .globl __Z5task1Pv .def __Z5task1Pv; .scl 2; .type 32; .endef__Z5task1Pv: ... 我们可以看到一个简单的cnt++,对应
CPU先将cnt的值读到寄存器eax中,然后将[eax] + 1,最后将eax的值返回到cnt中,这些操作不是**原子性质(atomic)**的,这就导致cnt被多个线程操作时,+1过程会被打断。 加入mutex保护临界资源 #include <pthread.h>#include <stdio.h>#define LOOP 1000000pthread_mutex_t mutex;int cnt = 0;int cs1 = 0, cs2 = 0;void* task(void* args) { while(1) { pthread_mutex_lock(&mutex); if(cnt >= LOOP) { pthread_mutex_unlock(&mutex); break; } cnt++; pthread_mutex_unlock(&mutex); if((int)args == 1) cs1 ++; else cs2++; } return NULL;}int main() { pthread_mutex_init(&mutex , NULL); pthread_t tid1; pthread_t tid2; /* create the thread */ pthread_create(&tid1, NULL, task, (void*)1); pthread_create(&tid2, NULL, task, (void*)2); /* wait for thread to exit */ pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf('cnt = %d cs1=%d cs2=%d total=%d\n', cnt,cs1,cs2,cs1+cs2); return 0;}输出:cnt = 1000000 cs1=517007 cs2=482993 total=1000000 |
|
来自: 山峰云绕 > 《Linux内核编写编译及原理剖析》