温州做微网站设计,在学做网站还不知道买什么好,canva可画ppt模板,9377传奇世界Linux线程同步与互斥
一、核心理论基础#xff1a;互斥与同步
1. 互斥#xff08;Mutex#xff09;#xff1a;临界资源的排他性访问
核心概念
临界资源#xff1a;多线程中需共同读写的资源#xff08;如全局变量、文件、硬件设备#xff09;#xff0c;同一时刻只…Linux线程同步与互斥一、核心理论基础互斥与同步1. 互斥Mutex临界资源的排他性访问核心概念临界资源多线程中需共同读写的资源如全局变量、文件、硬件设备同一时刻只能被一个线程访问。互斥通过锁机制保证临界资源的排他性访问避免多个线程同时操作导致数据不一致。原子操作加锁后到解锁前的代码段必须在一次线程调度中完整执行不可被其他线程打断。问题根源指令穿插执行以A为例其汇编指令至少包含3步读取变量A的值到寄存器寄存器中值1将结果写回变量A。若线程1执行完前2步被调度切换线程2继续操作A会导致最终结果小于预期数据一致性破坏。互斥锁使用步骤定义互斥锁pthread_mutex_t mutex;初始化锁pthread_mutex_init(mutex, NULL);NULL表示默认属性加锁pthread_mutex_lock(mutex);阻塞式若锁被占用则等待解锁pthread_mutex_unlock(mutex);释放锁唤醒等待线程销毁锁pthread_mutex_destroy(mutex);资源释放避免内存泄漏关键函数说明函数原型功能描述int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)初始化互斥锁attr为锁属性默认NULLint pthread_mutex_lock(pthread_mutex_t *mutex)阻塞加锁若锁已被占用则线程阻塞int pthread_mutex_trylock(pthread_mutex_t *mutex)非阻塞加锁锁被占用时直接返回错误不阻塞int pthread_mutex_unlock(pthread_mutex_t *mutex)解锁必须由加锁线程执行int pthread_mutex_destroy(pthread_mutex_t *mutex)销毁互斥锁需在解锁后执行2. 同步Semaphore有顺序的资源访问核心概念同步多线程按预定顺序执行本质是“带顺序约束的互斥”属于互斥的特例。信号量通过计数器控制资源访问权限支持线程间交叉释放如线程1释放信号量唤醒线程2。信号量使用步骤定义信号量sem_t sem;初始化信号量sem_init(sem, pshared, value);pshared0线程间使用pshared!0进程间使用value信号量初始值二值信号量为0/1计数信号量可大于1。P操作申请资源sem_wait(sem);信号量-1为0则阻塞V操作释放资源sem_post(sem);信号量1唤醒阻塞线程销毁信号量sem_destroy(sem);3. 互斥锁与信号量的区别对比维度互斥锁信号量释放主体必须由加锁线程释放可由其他线程释放交叉唤醒资源计数仅支持二值0/1独占资源支持计数≥0多资源共享适用场景临界资源排他访问单资源线程同步、多资源并发访问休眠允许临界区不可休眠避免死锁可适当休眠如等待资源4. 死锁多线程的“致命陷阱”死锁的四个必要条件缺一不可互斥条件资源只能被一个线程占用请求与保持线程持有部分资源同时请求其他资源不剥夺条件资源不可被强行剥夺只能主动释放循环等待多个线程形成资源请求循环如线程1等线程2的资源线程2等线程1的资源。二、实战代码解析从问题到解决方案示例1无锁场景——数据竞争问题01pthreadr.c代码功能两个线程同时对全局变量A执行5000次自增无锁保护观察数据不一致问题。#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includepthread.hintA0;// 临界资源全局变量void*th(void*arg){inti5000;while(i--){inttmpA;// 步骤1读取Aprintf(A is %d\n,tmp1);Atmp1;// 步骤3写回A}returnNULL;}intmain(intargc,char**argv){pthread_ttid1,tid2;pthread_create(tid1,NULL,th,NULL);pthread_create(tid2,NULL,th,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);return0;}运行结果与问题预期结果A最终为10000实际结果A通常小于10000如9876且每次运行结果不同。原因两个线程的A指令穿插执行导致数据覆盖。示例2互斥锁解决数据竞争02pthread_10000.c代码功能在示例1基础上添加互斥锁保护全局变量A的自增操作保证数据一致性。#includestring.h#includestdio.h#includepthread.h#includestdlib.h#includeunistd.hintA0;pthread_mutex_tmutex;// 定义互斥锁void*thread(void*arg){inti5000;while(i--){pthread_mutex_lock(mutex);// 加锁临界区开始inttempA;printf(A is %d\n,temp1);Atemp1;pthread_mutex_unlock(mutex);// 解锁临界区结束}returnNULL;}intmain(){pthread_tt1,t2;pthread_mutex_init(mutex,NULL);// 初始化锁pthread_create(t1,NULL,thread,NULL);pthread_create(t2,NULL,thread,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_mutex_destroy(mutex);// 销毁锁printf(A is %d\n,A);// 最终结果稳定为10000return0;}关键改进加锁后A的三步操作成为原子操作避免线程穿插运行结果A最终稳定为10000数据一致性得到保证。编译命令gcc 02pthread_10000.c -o lock_demo -lpthread示例3互斥锁错误使用——死锁风险03lock.c代码功能10个线程竞争3个柜台每个线程依次申请所有柜台锁演示死锁场景。#includestdio.h#includestdlib.h#includepthread.h#includeunistd.h#includetime.h#includestring.h#defineWIN3// 3个柜台#defineNUM_THREADS10// 10个线程pthread_mutex_tcounter_locks[WIN];// 柜台锁数组typedefstruct{intclient_id;// 线程编号}client_t;void*client(void*arg){client_t*client(client_t*)arg;intidclient-client_id;inti0;// 错误每个线程依次申请所有柜台锁for(i0;iWIN;i){pthread_mutex_lock(counter_locks[i]);printf(Client %d is in critical section (win%d)\n,id,i1);sleep(1);// 临界区休眠放大死锁概率pthread_mutex_unlock(counter_locks[i]);}pthread_exit(NULL);}intmain(){inti-1;pthread_tthreads[NUM_THREADS];client_t*clientsmalloc(sizeof(client_t)*NUM_THREADS);// 初始化柜台锁for(i0;iWIN;i)pthread_mutex_init(counter_locks[i],NULL);// 创建10个线程for(i0;iNUM_THREADS;i){clients[i].client_idi;pthread_create(threads[i],NULL,client,(void*)clients[i]);}// 回收线程for(i0;iNUM_THREADS;i){pthread_join(threads[i],NULL);}// 销毁锁for(i0;iWIN;i)pthread_mutex_destroy(counter_locks[i]);free(clients);return0;}死锁原因线程1持有win1锁申请win2锁线程2持有win2锁申请win1锁形成循环等待临界区包含sleep(1)延长锁持有时间死锁概率极高。解决思路所有线程按固定顺序申请锁如统一先申请win1再win2、win3打破循环等待条件。示例4非阻塞加锁——避免死锁04trylock.c代码功能10个线程竞争3个柜台使用pthread_mutex_trylock非阻塞加锁避免死锁。#includepthread.h#includestdio.h#includestdlib.h#includestring.h#includetime.h#includeunistd.hpthread_mutex_tmutex1,mutex2,mutex3;// 3个柜台锁void*th(void*arg){intret0;while(1){// 非阻塞申请win1锁retpthread_mutex_trylock(mutex1);if(0ret){printf(get win1...\n);sleep(rand()%51);// 模拟业务办理printf(release win1...\n);pthread_mutex_unlock(mutex1);break;}// 申请win1失败尝试win2elseif((retpthread_mutex_trylock(mutex2))0){printf(get win2...\n);sleep(rand()%51);printf(release win2...\n);pthread_mutex_unlock(mutex2);break;}// 申请win2失败尝试win3elseif((retpthread_mutex_trylock(mutex3))0){printf(get win3...\n);sleep(rand()%51);printf(release win3...\n);pthread_mutex_unlock(mutex3);break;}}returnNULL;}intmain(intargc,char**argv){inti0;srand(time(NULL));pthread_ttid[10]{0};// 初始化锁pthread_mutex_init(mutex1,NULL);pthread_mutex_init(mutex2,NULL);pthread_mutex_init(mutex3,NULL);// 创建10个线程for(i0;i10;i){pthread_create(tid[i],NULL,th,NULL);}// 回收线程for(i0;i10;i){pthread_join(tid[i],NULL);}// 销毁锁pthread_mutex_destroy(mutex1);pthread_mutex_destroy(mutex2);pthread_mutex_destroy(mutex3);return0;}核心优化使用pthread_mutex_trylock非阻塞加锁申请失败时立即尝试下一个资源不阻塞避免线程间循环等待彻底解决死锁问题运行结果10个线程依次抢占3个柜台无死锁正常释放资源。示例5信号量实现线程同步05sem_hw.c代码功能两个线程通过信号量实现同步交替输出“Hello”和“World”线程1输出后唤醒线程2线程2输出后唤醒线程1。#includestdio.h#includestdlib.h#includestring.h#includepthread.h#includeunistd.h#includetime.h#includesemaphore.hsem_tsem_H,sem_W;// 信号量sem_H控制Hellosem_W控制Worldvoid*th1(void*arg){inti10;while(i--){sem_wait(sem_H);// P操作申请Hello信号量初始为1printf(Hello\n);fflush(stdout);// 刷新缓冲区避免输出乱序sem_post(sem_W);// V操作释放World信号量唤醒线程2}returnNULL;}void*th2(void*arg){inti10;while(i--){sem_wait(sem_W);// P操作申请World信号量初始为0阻塞printf(World\n);sleep(1);// 模拟耗时操作sem_post(sem_H);// V操作释放Hello信号量唤醒线程1}returnNULL;}intmain(){pthread_ttid1,tid2;// 初始化信号量sem_H1线程1可先执行sem_W0线程2阻塞sem_init(sem_H,0,1);sem_init(sem_W,0,0);pthread_create(tid1,NULL,th1,NULL);pthread_create(tid2,NULL,th2,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);// 销毁信号量sem_destroy(sem_H);sem_destroy(sem_W);return0;}同步逻辑初始状态sem_H1sem_W0线程1申请sem_H成功输出“Hello”释放sem_Wsem_W1线程2申请sem_W成功输出“World”释放sem_Hsem_H1循环10次实现“Hello→World”交替输出。示例6计数信号量——多资源共享06semcount.c代码功能10个线程竞争3个柜台多资源使用计数信号量控制并发访问最多3个线程同时占用资源。#includepthread.h#includestdio.h#includestdlib.h#includestring.h#includetime.h#includeunistd.h#includesemaphore.hsem_tsem_WIN;// 计数信号量控制柜台资源void*th(void*arg){sem_wait(sem_WIN);// P操作申请柜台资源信号量-1printf(get win...\n);sleep(rand()%51);// 模拟业务办理随机耗时1-5秒printf(release win...\n);sem_post(sem_WIN);// V操作释放柜台资源信号量1returnNULL;}intmain(intargc,char**argv){inti0;srand(time(NULL));pthread_ttid[10]{0};// 初始化计数信号量初始值33个柜台资源sem_init(sem_WIN,0,3);// 创建10个线程for(i0;i10;i){pthread_create(tid[i],NULL,th,NULL);}// 回收线程for(i0;i10;i){pthread_join(tid[i],NULL);}// 销毁信号量sem_destroy(sem_WIN);return0;}核心逻辑信号量初始值为3表示最多3个线程同时占用柜台线程申请资源时sem_WIN-1释放时sem_WIN1当sem_WIN0时后续线程阻塞直到有线程释放资源运行结果始终最多3个线程同时办理业务无资源竞争。三、关键注意事项与避坑指南1. 互斥锁使用规范临界区最小化加锁后仅包含必要的操作如读写临界资源避免休眠、耗时操作如sleep、网络请求加解锁成对出现避免“加锁后未解锁”导致死锁或“解锁未加锁”导致崩溃统一锁顺序多锁场景下所有线程按固定顺序申请锁如先锁A再锁B打破循环等待条件。2. 信号量使用规范区分二值与计数信号量二值信号量0/1用于互斥计数信号量≥1用于多资源共享信号量初始值合理设置根据资源数量设置如3个柜台则初始值为3避免信号量泄露申请资源后必须释放即使线程异常退出可通过pthread_cleanup_push注册清理函数。3. 死锁预防方法破坏四个必要条件之一即可避免循环等待统一锁顺序避免请求与保持一次性申请所有资源允许资源剥夺如使用非阻塞加锁弱化互斥条件如使用读写锁允许多读单写。四、总结互斥与同步的适用场景场景推荐方案核心原因单资源排他访问如全局变量互斥锁实现简单效率高多资源并发访问如多个柜台计数信号量支持资源数量控制线程间顺序依赖如A执行后B才能执行信号量支持交叉唤醒同步灵活避免死锁的抢占场景非阻塞加锁trylock申请失败时可尝试其他资源