线程同步与互斥---线程互斥 目录程线程间的互斥相关背景概念1.线程互斥1.见一种现象(数据不一致问题)2.解决这个问题(锁pthread锁)3.理解为什么数据会不一致认识加锁的接口4.理解锁线程是共享地址空间的---线程会共享大部分资源---那么在多线程访问这个共享资源时我们把共享资源叫做公共资源各种情况的数据不一致问题---解决这些问题同步和互斥程线程间的互斥相关背景概念• 共享资源• 临界资源多线程执行流被保护的共享的资源就叫做临界资源• 临界区每个线程内部访问临界资源的代码就叫做临界区• 互斥任何时刻互斥保证有且只有一个执行流进入临界区访问临界资源通常对临界资源起 保护作用• 原子性后面讨论如何实现不会被任何调度机制打断的操作该操作只有两态要么完成 要么未完成1.线程互斥1.见一种现象(数据不一致问题)TestMutex.cc// 操作共享变量会有问题的售票系统代码 #include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.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(void) { 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); }MakefileTestMutex:TestMutex.cc g -o $ $^ -stdc11 -lpthread .PHONY:clean clean: rm -f TestMutexrootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-27/5.mutex# make g -o TestMutex TestMutex.cc -stdc11 -lpthread rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-27/5.mutex# ./TestMutex thread 2 sells ticket:1000 thread 1 sells ticket:1000 thread 3 sells ticket:1000 thread 4 sells ticket:1000 thread 2 sells ticket:996 thread 1 sells ticket:995 thread 3 sells ticket:995 ....... thread 3 sells ticket:10 thread 4 sells ticket:9 thread 2 sells ticket:8 thread 1 sells ticket:7 thread 3 sells ticket:6 thread 4 sells ticket:5 thread 2 sells ticket:4 thread 1 sells ticket:3 thread 3 sells ticket:2 thread 4 sells ticket:1 thread 2 sells ticket:0 thread 1 sells ticket:-1 thread 3 sells ticket:-2抢票抢到负数要把临界区保护起来2.解决这个问题(锁pthread锁)pthread_mutex_init 初始化互斥锁NAMEpthread_mutex_destroy,pthread_mutex_init - destrobyand initialize a mutexSYNOPSIS#include pthread.hint pthread_mutex_destroy(pthread_mutex_t *mutex);int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock//加锁pthread_mutex_unlock//解锁TestMutex.cc// 操作共享变量会有问题的售票系统代码 #include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h int ticket 1000; pthread_mutex_t lock PTHREAD_MUTEX_INITIALIZER; void *route(void *arg) { char *id (char *)arg; while (1) { pthread_mutex_lock(lock); if (ticket 0) // 1. 判断 { usleep(1000); // 模拟抢票花的时间 printf(%s sells ticket:%d\n, id, ticket); // 2. 抢到了票 ticket--; // 3. 票数-- pthread_mutex_unlock(lock); } else { pthread_mutex_unlock(lock); break; } } return nullptr; } int main(void) { 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); }被保护起来的共享资源叫做临界资源3.理解为什么数据会不一致认识加锁的接口多线程---抢票为什么会减到负数--不是主要矛盾但是和它有关1.ticket--不是原子的C语言-汇编语言暂时 当前一条汇编就是原子的3条汇编切换数据不一致问题CPU在执行执行流的代码1.内存把ticket值载入到CPU的ebx寄存器中2.CPU内做--3.减完写回内存更改pc指针是一个CPU寄存器存储下一条要执行的指令的内存地址票数为什么减到了负数if (ticket 0) ----也是一种计算逻辑计算---CPU判断1.载入2.判断是主要矛盾多个线程同时进入串行式--从而减到负数一个全局资源没有加保护可能会有并发问题---线程安全问题多线程中制造更多的并发更多的切换---切换的时间点是什么时候1.时间片 2.阻塞是IO 3.sleep等...(切走)陷入内核选择新的从内核态返回用户态的时候进行检查引入锁:pthread_mutex_t 互斥锁/互斥量#include pthread.hint pthread_mutex_destroy(pthread_mutex_t *mutex);//释放int pthread_mutex_init(pthread_mutex_t *restricct mutexconst pthread_mutexattr_t *restrict attr);//初始化全局pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;全局锁不需要被释放程序运行结束会自动释放申请锁int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁int pthread_mutex_trylock(pthread_mutex_t *mutex);//申请锁的非阻塞版本int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁加锁解锁都是原子的加锁要竞争申请锁多线程都得先看到锁锁本身就是临界资源申请锁的过程必须是原子的申请成功继续向后运行访问临界区代码访问临界资源失败阻塞挂起申请执行流|锁提供的能力的本质执行临界区代码由并行转换为串行(在我执行期间不会被打扰也是一种变相的原子性的表现)加速尽量加锁的范围粒度要比较细尽可能的不要包含太多的非临界区代码testMutex.cpp// 操作共享变量会有问题的售票系统代码 #include iostream #include string #include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h int ticket 1000; class ThreadData { public: ThreadData(const std::string n, pthread_mutex_t lock) : name(n), lockp(lock) { } ~ThreadData() {} std::string name; pthread_mutex_t *lockp; }; //加速尽量加锁的范围粒度要比较细尽可能的不要包含太多的非临界区代码 void *route(void *arg) { ThreadData *td static_castThreadData *(arg); while (1) { pthread_mutex_lock(td-lockp); //if这部分是临界区其他是非临界区保护临界区通过加速和解锁 if (ticket 0) { usleep(1000); printf(%s sells ticket:%d\n, td-name.c_str(), ticket); ticket--; pthread_mutex_unlock(td-lockp); } else { pthread_mutex_unlock(td-lockp); break; } } return nullptr; } int main(void) { pthread_mutex_t lock; pthread_mutex_init(lock,nullptr);//初始化锁 pthread_t t1, t2, t3, t4; ThreadData *td1 new ThreadData(thread 1,lock); pthread_create(t1, NULL, route, td1); ThreadData *td2 new ThreadData(thread 2,lock); pthread_create(t2, NULL, route, td2); ThreadData *td3 new ThreadData(thread 3,lock); pthread_create(t3, NULL, route, td3); ThreadData *td4 new ThreadData(thread 4,lock); pthread_create(t4, NULL, route, td4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); pthread_mutex_destroy(lock);//销毁锁 return 0; }MakefiletestMutex:testMutex.cpp g -o $ $^ -stdc11 -lpthread .PHONY:clean clean: rm -f testMutexrootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-27.2# make g -o testMutex testMutex.cpp -stdc11 -lpthread rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-27.2# ./testMutex thread 1 sells ticket:1000 thread 1 sells ticket:999 thread 1 sells ticket:998 thread 1 sells ticket:997 thread 1 sells ticket:996 ........ thread 3 sells ticket:4 thread 3 sells ticket:3 thread 3 sells ticket:2 thread 3 sells ticket:1全局的testMutex.cpp// 操作共享变量会有问题的售票系统代码 #include iostream #include string #include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h int ticket 1000; pthread_mutex_t glock PTHREAD_MUTEX_INITIALIZER;//全局的初始化 class ThreadData { public: ThreadData(const std::string n, pthread_mutex_t lock) : name(n), lockp(lock) { } ~ThreadData() {} std::string name; pthread_mutex_t *lockp; }; //加速尽量加锁的范围粒度要比较细尽可能的不要包含太多的非临界区代码 void *route(void *arg) { ThreadData *td static_castThreadData *(arg); while (1) { //pthread_mutex_lock(td-lockp); pthread_mutex_lock(glock); //if这部分是临界区其他是非临界区保护临界区通过加速和解锁 if (ticket 0) { usleep(1000); printf(%s sells ticket:%d\n, td-name.c_str(), ticket); ticket--; //pthread_mutex_unlock(td-lockp); pthread_mutex_unlock(glock); } else { //pthread_mutex_unlock(td-lockp); pthread_mutex_unlock(glock); break; } } return nullptr; } int main(void) { pthread_mutex_t lock; pthread_mutex_init(lock,nullptr);//初始化锁 pthread_t t1, t2, t3, t4; ThreadData *td1 new ThreadData(thread 1,lock); pthread_create(t1, NULL, route, td1); ThreadData *td2 new ThreadData(thread 2,lock); pthread_create(t2, NULL, route, td2); ThreadData *td3 new ThreadData(thread 3,lock); pthread_create(t3, NULL, route, td3); ThreadData *td4 new ThreadData(thread 4,lock); pthread_create(t4, NULL, route, td4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); pthread_mutex_destroy(lock);//销毁锁 return 0; }MakefiletestMutex:testMutex.cpp g -o $ $^ -stdc11 -lpthread .PHONY:clean clean: rm -f testMutexrootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-27.3# make g -o testMutex testMutex.cpp -stdc11 -lpthread rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-27.3# ./testMutex thread 1 sells ticket:1000 thread 1 sells ticket:999 thread 1 sells ticket:998 thread 1 sells ticket:997 thread 1 sells ticket:996 thread 1 sells ticket:995 ..... thread 1 sells ticket:5 thread 1 sells ticket:4 thread 1 sells ticket:3 thread 1 sells ticket:2 thread 1 sells ticket:1进程间互斥共享内存shm,(pthread_mutex_t*)shm我们有两个线程先创建一个共享内存。锁本质就是变量直接就用共享内存头部的位置让两个线程都能看到把头部位置强转成pthread_mutex_t*。就可以直接使用init初始化destroy做销毁。对临界资源进行保护本质其实就是用锁来对临界区代码进行保护问题1如果有一个线程不遵守你的规则---写bug---所有线程都必须遵守问题2加速之后在临界区内部允许线程切换吗切换了会怎么样--不怎么样所有线程必须等我跑完允许切换的---但是不担心因为我当前线程并没有释放锁我是持有锁被切换的即使我不在其他线程也得等我回来执行完代码释放锁其他线程才能展开锁的竞争进入临界区站在外边的人是如何看待你的自习过程呢要么不用要么用完对外边的人才有意义原子性你的自习对外边的人是原子的4.理解锁锁的原理1.硬件及实现关闭时钟中断2.软件级实现• 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和 内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周 期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。锁本质上就是一个标志位理解锁的前提进程/线程切换CPU内的寄存器硬件只有一套但CPU寄存器内的数据可以有多份各自一份当前执行流的上下文|换句话说如果把一个变量的内容交换到CPU寄存器内部本质是把该变量的内容获取到当前执行流的上下文中|当前CPU寄存器的硬件上下文(其实就是各个寄存器的内容)属于进程/线程私有的|所以我们用swapexchange将内存中的变量交换到CPU的寄存器中本质是当前线程/进程,在获取锁因为是交换不是拷贝所以内部1只有一份所以谁申请到谁持有锁线程A/B进寄存器要把寄存器清零但是清的都是自己的上下文数据。彼此不会影响也不怕中断exchange无法被中断线程A被切走带走自己的%al:1谁交换成功谁就持有锁了Mutex就相当于这把锁谁持有这个1谁就持有锁所以释放锁/解锁的时候只要往Mutex写1就可以了C11也提供了std::mutex lock;#includemutextestMutex.cpp// 操作共享变量会有问题的售票系统代码 #include iostream #includemutex #include string #include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h int ticket 1000; pthread_mutex_t glock PTHREAD_MUTEX_INITIALIZER;//全局的初始化 std::mutex cpp_lock; class ThreadData { public: ThreadData(const std::string n, pthread_mutex_t lock) : name(n), lockp(lock) { } ~ThreadData() {} std::string name; pthread_mutex_t *lockp; }; //加速尽量加锁的范围粒度要比较细尽可能的不要包含太多的非临界区代码 void *route(void *arg) { ThreadData *td static_castThreadData *(arg); while (1) { cpp_lock.lock(); //pthread_mutex_lock(td-lockp); //pthread_mutex_lock(glock); //if这部分是临界区其他是非临界区保护临界区通过加速和解锁 if (ticket 0) { usleep(1000); printf(%s sells ticket:%d\n, td-name.c_str(), ticket); ticket--; //pthread_mutex_unlock(td-lockp); //pthread_mutex_unlock(glock); cpp_lock.unlock(); } else { //pthread_mutex_unlock(td-lockp); //pthread_mutex_unlock(glock); cpp_lock.unlock(); break; } } return nullptr; } int main(void) { pthread_mutex_t lock; pthread_mutex_init(lock,nullptr);//初始化锁 pthread_t t1, t2, t3, t4; ThreadData *td1 new ThreadData(thread 1,lock); pthread_create(t1, NULL, route, td1); ThreadData *td2 new ThreadData(thread 2,lock); pthread_create(t2, NULL, route, td2); ThreadData *td3 new ThreadData(thread 3,lock); pthread_create(t3, NULL, route, td3); ThreadData *td4 new ThreadData(thread 4,lock); pthread_create(t4, NULL, route, td4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); pthread_mutex_destroy(lock);//销毁锁 return 0; }MakefiletestMutex:testMutex.cpp g -o $ $^ -stdc11 -lpthread .PHONY:clean clean: rm -f testMutexMutex.hpp#pragma once #include iostream #include pthread.h namespace MutexMudole { class Mutex { public: Mutex() { pthread_mutex_init(_mutex, nullptr); } void Lock() { int n pthread_mutex_lock(_mutex); (void)n; } void Unlock() { int n pthread_mutex_unlock(_mutex); (void)n; } ~Mutex() { pthread_mutex_destroy(_mutex); } private: pthread_mutex_t _mutex; }; }testMutex.cpp// 操作共享变量会有问题的售票系统代码 #include iostream #includemutex #include string #include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h #includeMutex.hpp using namespace MutexMudole; int ticket 1000; //pthread_mutex_t glock PTHREAD_MUTEX_INITIALIZER;//全局的初始化 //std::mutex cpp_lock; class ThreadData { public: ThreadData(const std::string n, Mutex lock) : name(n), lockp(lock) { } ~ThreadData() {} std::string name; Mutex *lockp; }; //加速尽量加锁的范围粒度要比较细尽可能的不要包含太多的非临界区代码 void *route(void *arg) { ThreadData *td static_castThreadData *(arg); while (1) { //cpp_lock.lock(); //pthread_mutex_lock(td-lockp); //pthread_mutex_lock(glock); //if这部分是临界区其他是非临界区保护临界区通过加速和解锁 td-lockp-Lock(); if (ticket 0) { usleep(1000); printf(%s sells ticket:%d\n, td-name.c_str(), ticket); ticket--; //pthread_mutex_unlock(td-lockp); //pthread_mutex_unlock(glock); //cpp_lock.unlock(); td-lockp-Unlock(); } else { //pthread_mutex_unlock(td-lockp); //pthread_mutex_unlock(glock); //cpp_lock.unlock(); td-lockp-Unlock(); break; } } return nullptr; } int main(void) { // pthread_mutex_t lock; // pthread_mutex_init(lock,nullptr);//初始化锁 Mutex lock; pthread_t t1, t2, t3, t4; ThreadData *td1 new ThreadData(thread 1,lock); pthread_create(t1, NULL, route, td1); ThreadData *td2 new ThreadData(thread 2,lock); pthread_create(t2, NULL, route, td2); ThreadData *td3 new ThreadData(thread 3,lock); pthread_create(t3, NULL, route, td3); ThreadData *td4 new ThreadData(thread 4,lock); pthread_create(t4, NULL, route, td4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); //pthread_mutex_destroy(lock);//销毁锁 return 0; }MakefiletestMutex:testMutex.cpp g -o $ $^ -stdc11 -lpthread .PHONY:clean clean: rm -f testMutexrootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-27.4# make g -o testMutex testMutex.cpp -stdc11 -lpthread rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-27.4# ./testMutex thread 1 sells ticket:1000 thread 1 sells ticket:999 thread 1 sells ticket:998 thread 1 sells ticket:997 ..... thread 2 sells ticket:3 thread 2 sells ticket:2 thread 2 sells ticket:1RAII风格的互斥锁Mutex.hpp#pragma once #include iostream #include pthread.h namespace MutexMudole { class Mutex { public: Mutex() { pthread_mutex_init(_mutex, nullptr); } void Lock() { int n pthread_mutex_lock(_mutex); (void)n; } void Unlock() { int n pthread_mutex_unlock(_mutex); (void)n; } ~Mutex() { pthread_mutex_destroy(_mutex); } private: pthread_mutex_t _mutex; }; class LockGuard { public: LockGuard(Mutex mutex):_mutex(mutex) { _mutex.Lock(); } ~LockGuard() { _mutex.Unlock(); } private: Mutex _mutex; }; }testMutex.cpp// 操作共享变量会有问题的售票系统代码 #include iostream #include mutex #include string #include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h #include Mutex.hpp using namespace MutexMudole; int ticket 1000; class ThreadData { public: ThreadData(const std::string n, Mutex lock) : name(n), lockp(lock) { } ~ThreadData() {} std::string name; Mutex *lockp; }; // 加速尽量加锁的范围粒度要比较细尽可能的不要包含太多的非临界区代码 void *route(void *arg) { ThreadData *td static_castThreadData *(arg); while (1) { {//临界区 LockGuard guard(*td-lockp); // 加锁完成RAII风格的互斥锁的实现 if (ticket 0) { usleep(1000); printf(%s sells ticket:%d\n, td-name.c_str(), ticket); ticket--; } else { break; } } } return nullptr; } int main(void) { Mutex lock; pthread_t t1, t2, t3, t4; ThreadData *td1 new ThreadData(thread 1, lock); pthread_create(t1, NULL, route, td1); ThreadData *td2 new ThreadData(thread 2, lock); pthread_create(t2, NULL, route, td2); ThreadData *td3 new ThreadData(thread 3, lock); pthread_create(t3, NULL, route, td3); ThreadData *td4 new ThreadData(thread 4, lock); pthread_create(t4, NULL, route, td4); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); return 0; }MakefiletestMutex:testMutex.cpp g -o $ $^ -stdc11 -lpthread .PHONY:clean clean: rm -f testMutex//代码块进代码块开空间出代码块释放空间。所以两个不冲突不会重定义{int a10;}int a20;感谢你的观看期待我们下次再见

本月热点