?线程互斥? 库函数strncpy?进程线程间的互斥相关背景概念临界资源:多线程执⾏流共享的资源就叫做临界资源临界区:每个线程内部,访问临界资源的代码,就叫做临界区互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起保护作⽤原⼦性(后⾯讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成?互斥量mutex⼤部分情况,线程使⽤的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程⽆法获得这种变量。但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。多个线程并发的操作共享变量,会带来⼀些问题。
makefile文件
代码语言:JavaScript代码运行次数:0运行复制
bin=ticketcc=g++src=$(wildcard *.cc)obj=$(src:.cc=.o)$(bin):$(obj)$(cc) -o $@ $^ -lpthread%.o:%.cc@echo "Comiling $<p>代码:</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="prism-token token line-numbers javascript">#include <stdio.h>#include <string>#include <string.h>#include <pthread.h>#include <unistd.h>int ticket = 100;void* routine(void* args){ char *id = (char*)args; // std::string id = static_cast<const char>(args); while(1) { if(ticket > 0) { usleep(10000); printf("%s sells ticket:%dn", id, ticket); ticket--; } else { break; } } return NULLptr;}int main(void){ pthread_t t1, t2 , t3, t4; pthread_create(&t1, nullptr, routine, (void*)"thread 1"); pthread_create(&t2, nullptr, routine, (void*)"thread 2"); pthread_create(&t3, nullptr, routine, (void*)"thread 3"); pthread_create(&t4, nullptr, routine, (void*)"thread 4"); pthread_join(t1, nullptr); pthread_join(t2, nullptr); pthread_join(t3, nullptr); pthread_join(t4, nullptr); return 0;}</const></unistd.h></pthread.h></string.h></string></stdio.h>




if语句判断条件为真以后,代码可以并发的切换到其他进程usleep这个模拟夜漫长业务的过程这个漫长的业务过程中,可能有多个线程会进入该代码段–ticket操作本身就不是一个原子操作
取出ticket–部分的汇编代码
代码语言:javascript代码运行次数:0运行复制
objdump -d a.out > test.objdump 152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax 600b34 <ticket> 153 400651: 83 e8 01 sub $0x1,%eax154 400654: 89 05 da 04 20 00 mov%eax,0x2004da(%rip) 600b34 <ticket></ticket></ticket>
–操作并不是原子操作而是对应三条汇编指令:
load将共享变量体的从内存加载到寄存器update更新寄存器里面的只执行复议操作store:将新值从寄存器写回共享变量ticket的内存地址
要解决以上问题需要做到三点:
代码必须要有互斥行为:当代码进入临界区执行时,不允许其他进程进入该临界区如果多个线程同时要求进入临界区的代码,并且临界区没有线程在执行,那么只能一个线程进入该临界区如果现场不在临界区中执行,那么该现场就不能阻止其他进程进入临界区
要做到这三点,本身是上就是需要一把锁,linux上提供这把锁叫互斥量

互斥量的接口 初始化互斥量的两种方法
方法1:静态分配代码语言:javascript代码运行次数:0运行复制
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
⽅法2,动态分配: int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参数: mutex:要初始化的互斥量 attr:这是一个指向 pthread_mutexattr_t 类型对象的指针,该类型用于定义互斥锁的属性。如果将其设置为 NULL
销毁互斥量 销毁互斥量需要注意:
使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁不要销毁⼀个已经加锁的互斥量已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁代码语言:javascript代码运行次数:0运行复制
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥量加锁和解锁
代码语言:javascript代码运行次数:0运行复制
int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);返回值:成功返回o,失败返回错误号
调用pthread_ lock时,可能会遇到以下情况:
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。
改进上面的售票系统:
代码语言:javascript代码运行次数:0运行复制
#include <stdio.h>#include <string>#include <string.h>#include <pthread.h>#include <unistd.h>int ticket = 100;pthread_mutex_t mutex;void* routine(void* args){ char *id = (char*)args; // std::string id = static_cast<const char>(args); while(1) { pthread_mutex_lock(&mutex); if(ticket > 0) { usleep(1000); printf("%s sells ticket:%dn", id, ticket); ticket--; pthread_mutex_unlock(&mutex); } else { pthread_mutex_unlock(&mutex); break; } } return nullptr;}int main(void){ pthread_t t1, t2 , t3, t4; pthread_create(&t1, nullptr, routine, (void*)"thread 1"); pthread_create(&t2, nullptr, routine, (void*)"thread 2"); pthread_create(&t3, nullptr, routine, (void*)"thread 3"); pthread_create(&t4, nullptr, routine, (void*)"thread 4"); pthread_join(t1, nullptr); pthread_join(t2, nullptr); pthread_join(t3, nullptr); pthread_join(t4, nullptr); pthread_mutex_destroy(&mutex); return 0;}</const></unistd.h></pthread.h></string.h></string></stdio.h>


?线程同步?条件变量当⼀个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如⼀个线程访问队列时,发现队列为空,它只能等待,只到其它线程将⼀个节点添加到队列中。这种情况就需要⽤到条件变量。?同步概念与竞态条件同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免饥饿问题,叫做同步竞态条件:因为时序问题,⽽导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也 不难理解? 条件变量函数
初始化
代码语言:javascript代码运行次数:0运行复制
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t*restrict attr);
参数: cond:要初始化的条件变量 attr: NULL
销毁:
代码语言:javascript代码运行次数:0运行复制
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满⾜
代码语言:javascript代码运行次数:0运行复制
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrictmutex);参数:cond:要在这个条件变量上等待mutex:互斥量,后面详细解释
唤醒等待
代码语言:javascript代码运行次数:0运行复制
int pthread_cond_broadcast(pthread_cond_t *cond);int pthread_cond_signal(pthread_cond_t *cond);
简单案例:
我们先使用PTHREAD_COND/MUTEX_INITIALIZER进行测试,对其他细节暂不追究然后将接口更改成为使用pthread_cond_init/pthread_cond_destroy的方式,方便后续进行封装代码语言:javascript代码运行次数:0运行复制
#include <iostream>#include <string.h>#include <unistd.h>#include <pthread.h>pthread_cond_t cond = PTHREAD_COND_INITIALIZER;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* active(void* args){ std::string name = static_cast<const char>(args); while(true) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); std::cout<figure class=""><img src="https://img.php.cn/upload/article/001/503/042/174487358175324.jpg" alt="【Linux学习指南】线程同步与互斥"></figure></const></pthread.h></unistd.h></string.h></iostream>