一、进程线程间互斥相关概念理解共享资源临界资源多线程执行流被保护的共享的资源就叫做临界资源临界区每个线程内部访问临界资源的代码就叫做临界区互斥任何时刻互斥保证有且只有一个执行流进入临界区访问临界资源通常对临界资源起保护作用原子性后面讨论如何实现不会被任何调度机制打断的操作该操作只有两态要么完成要么未完成二、互斥量 mutex大部分情况线程使用的数据都是局部变量变量的地址空间在线程栈空间内这种情况变量归属单个线程其他线程无法获得这种变量。但有时候很多变量都需要在线程间共享这样的变量称为共享变量可以通过数据的共享完成线程之间的交互。多个线程并发的操作共享变量会带来一些问题。下面看一个模拟抢票的案例4个线程同时抢票#include iostream #include pthread.h #include unistd.h #include stdlib.h int ticket 1000; void* route(void* arg) { char* id (char*)arg; while(1) { if(ticket 0) { usleep(1000); printf(%s sells ticket: %d \n, id, ticket); ticket--; } else { break; } } return nullptr; } int main() { pthread_t t1, t2, t3, t4; pthread_create(t1, NULL, route, (void*)thread-1); pthread_create(t2, NULL, route, (void*)thread-2); pthread_create(t3, NULL, route, (void*)thread-3); pthread_create(t4, NULL, route, (void*)thread-4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); return 0; }运行结果...... thread-2 sells ticket: 7 thread-4 sells ticket: 6 thread-3 sells ticket: 6 thread-1 sells ticket: 4 thread-2 sells ticket: 3 thread-4 sells ticket: 2 thread-3 sells ticket: 2 thread-1 sells ticket: 0 thread-2 sells ticket: -1 thread-4 sells ticket: -2会发现票数怎么是负的呢if语句判断条件为真以后代码可以并发的切换到其他线程usleep这个模拟漫长业务的过程在这个漫长的业务过程中可能有很多个线程会进入该代码段--ticket操作本身就不是一个原子操作也就是说这一段代码被重入了当一个线程进入if判断后就被切走了其他线程会进入route函数将票抢完但是被切走的线程回来后继续执行后面的代码因为已经判断了是有票的所以就会出现负数的票取出 ticket-- 部分的汇编代码objdump -d a.out test.objdump152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 ticket 153 400651: 83 e8 01 sub $0x1,%eax 154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 ticket--操作并不是原子操作而是对应三条汇编指令load将共享变量 ticket 从内存加载到寄存器中update更新寄存器里面的值执行 - 1 操作store将新值从寄存器写回共享变量 ticket 的内存地址要解决以上问题需要做到三点:代码必须要有互斥行为当代码进入临界区执行时不允许其他线程进入该临界区。如果多个线程同时要求执行临界区的代码并且临界区没有线程在执行那么只能允许一个线程进入该临界区。如果线程不在临界区中执行那么该线程不能阻止其他线程进入临界区。要做到这三点本质上就是需要一把锁。Linux 上提供的这把锁叫互斥量。三、互斥量的接口1.初始化互斥量初始化互斥量有两种方法方法 1静态分配pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;方法 2动态分配int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);参数mutex要初始化的互斥量attrNULL2.销毁互斥量销毁互斥量需要注意使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁不要销毁一个已经加锁的互斥量已经销毁的互斥量要确保后面不会有线程再尝试加锁int pthread_mutex_destroy(pthread_mutex_t *mutex);3. 互斥量加锁和解锁int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);返回值成功返回0失败返回错误号调用pthread_lock时可能会遇到以下情况互斥量处于未锁状态该函数会将互斥量锁定同时返回成功发起函数调用时其他线程已经锁定互斥量或者存在其他线程同时申请互斥量但没有竞争到互斥量那么pthread_lock调用会陷入阻塞执行流被挂起等待互斥量解锁。用锁对上面的售票系统进行修改#include iostream #include pthread.h #include unistd.h #include stdlib.h int ticket 1000; pthread_mutex_t mutex; void* route(void* arg) { char* id (char*)arg; while(1) { pthread_mutex_lock(mutex); if(ticket 0) { usleep(1000); printf(%s sells ticket: %d \n, id, ticket); ticket--; pthread_mutex_unlock(mutex); } else { pthread_mutex_unlock(mutex); break; } } return nullptr; } int main() { pthread_t t1, t2, t3, t4; pthread_mutex_init(mutex, NULL); pthread_create(t1, NULL, route, (void*)thread-1); pthread_create(t2, NULL, route, (void*)thread-2); pthread_create(t3, NULL, route, (void*)thread-3); pthread_create(t4, NULL, route, (void*)thread-4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); pthread_mutex_destroy(mutex); return 0; }运行结果...... thread-2 sells ticket: 4 thread-2 sells ticket: 3 thread-2 sells ticket: 2 thread-2 sells ticket: 1 zhangsanhcss-ecs-f571:~/learn_-linux/thread/test_pthread_mutex$思考1.加锁解锁在代码的什么地方比较合适加锁会导致效率降低所以加锁的粒度必须足够细加锁相当于把并行的线程变成了串行的线程2.mutex是共享资源他保护别人谁来保护它lock unlock被设计成为了原子的3.会不会一些线程遵守加锁和解锁一些线程不遵守不会。对临界资源进行保护加锁和解锁是所有相关线程的共识经过上面的例子已经意识到单纯的i或者i都不是原子的有可能会有数据一致性问题为了实现互斥锁操作大多数体系结构都提供了swap或exchange指令该指令的作用是把寄存器和内存单元的数据相交换由于只有一条指令保证了原子性即使是多处理器平台访问内存的总线周期也有先后一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。四、理解加锁和解锁的过程lock和unlock的伪代码加锁的过程线程把 al 寄存器置为0交换al与mutex交换完成al 是 1 mutex 是 0al 0 加锁完成返回函数继续执行函数的代码已经加锁又来一个线程线程把 al 寄存器置为0交换al与mutex交换完成al 是 0mutex 是 0 因为锁已经被拿走了al 是 0 挂起等待当收到时钟中断时被唤醒goto去执行lock代码解锁过程将mutex的1还回去即置1唤醒被挂起的线程返回函数五、互斥量的封装1.常规版本#pragma once #include iostream #include pthread.h class Mutex { public: Mutex() { pthread_mutex_init(_lock, NULL); } void Lock() { pthread_mutex_lock(_lock); } void Unlock() { pthread_mutex_unlock(_lock); } ~Mutex() { pthread_mutex_destroy(_lock); } private: pthread_mutex_t _lock; };使用#include iostream #include pthread.h #include unistd.h #include stdlib.h #include Mutex.hpp int ticket 1000; pthread_mutex_t mutex; Mutex lock; void* route(void* arg) { char* id (char*)arg; while(1) { // pthread_mutex_lock(mutex); lock.Lock(); if(ticket 0) { usleep(1000); printf(%s sells ticket: %d \n, id, ticket); ticket--; // pthread_mutex_unlock(mutex); lock.Unlock(); } else { // pthread_mutex_unlock(mutex); lock.Unlock(); break; } } return nullptr; } int main() { pthread_t t1, t2, t3, t4; pthread_mutex_init(mutex, NULL); pthread_create(t1, NULL, route, (void*)thread-1); pthread_create(t2, NULL, route, (void*)thread-2); pthread_create(t3, NULL, route, (void*)thread-3); pthread_create(t4, NULL, route, (void*)thread-4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); pthread_mutex_destroy(mutex); return 0; }2.RAII风格的互斥锁class LockGuard { public: LockGuard(Mutex mutex):_mutex(mutex) { _mutex.Lock(); } ~LockGuard() { _mutex.Unlock(); } private: Mutex _mutex; };使用#include iostream #include pthread.h #include unistd.h #include stdlib.h #include Mutex.hpp int ticket 1000; pthread_mutex_t mutex; Mutex lock; void *route(void *arg) { char *id (char *)arg; while (1) { // 临界区 { LockGuard lockguard(lock); // pthread_mutex_lock(mutex); // lock.Lock(); if (ticket 0) { usleep(1000); printf(%s sells ticket: %d \n, id, ticket); ticket--; // pthread_mutex_unlock(mutex); // lock.Unlock(); } else { // pthread_mutex_unlock(mutex); // lock.Unlock(); break; } } } return nullptr; } int main() { pthread_t t1, t2, t3, t4; // pthread_mutex_init(mutex, NULL); pthread_create(t1, NULL, route, (void *)thread-1); pthread_create(t2, NULL, route, (void *)thread-2); pthread_create(t3, NULL, route, (void *)thread-3); pthread_create(t4, NULL, route, (void *)thread-4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); // pthread_mutex_destroy(mutex); return 0; }RAII 风格的互斥锁C11 也有std::mutex mtx; std::lock_guardstd::mutex guard(mtx);