怎么用ftp修改网站图片,做网站买过域名之后,中国建设银行网站上不去,wordpress七牛云作者主页#xff1a; 作者主页 本篇博客专栏#xff1a;Linux 创作时间 #xff1a;2024年11月24日 线程ID及进程地址空间布局
先看一下这段代码#xff1a;
运行一下#xff1a;
运行这个代码之后#xff0c;我们看到的这个很大的数字就是线程id#xff0c;然后… 作者主页 作者主页 本篇博客专栏Linux 创作时间 2024年11月24日 线程ID及进程地址空间布局
先看一下这段代码
运行一下
运行这个代码之后我们看到的这个很大的数字就是线程id然后我们可以用ps -aL命令来查看一下
这里面的第一串数字就是pid第二串数字就是LWP我们可以看到LWP跟线程id是不一样的。所以给用户提供的线程id不是内核中的lwp而是pthread库自己维护的一个值
通过这个代码我们把上面的数字转换成十六进制可以看出这是一个地址 理解库 动态库在运行时被加载动态库没被加载前在磁盘当中 pthread库本质也是一个文件 我们刚刚写的可执行程序他也是一个文件他也在磁盘当中可执行程序内部用线程库来创建多线程。 程序运行的时候会变成一个进程加载到内存当中内存中就有了该进程的代码和数据。创建线程的时候我们要先把线程库加载到内存当中然后再映射到该进程的地址空间才能用映射要映射到堆栈之间的共享区如果有多个多线程进程只需要将共享取得代码经过页表映射到已经加载到内存的库此时多个进程就可以使用同一个库里的方法来创建线程 Linux只维护轻量级进程linux中的pcb里与执行流相关的属性都是轻量级进程的属性所有的属性都是围绕lwp展开的。我们在用户层的概念是线程要的是线程的id与线程相关的内容在Linux中是没有的它没有维护。所以这部分属性由库来进行维护。 为了更好的管理线程创建线程时库会为我们的每一个线程申请一个内存块(描述线程的相关结构体字段属性)。 未来要找一个线程的所有属性只要找到线程控制块的地址即可。所以pthread_t id就是一个地址。 pthread_t类型的线程ID本质就是线程属性集合的起始虚拟地址 ---- 在pthread库中维护。 有这些打印出来的值可以看出全部变量是受所有线程所控制的 但如果我们这样去写的话 如果我们想让两个线程各自私有一份变量g有一个编译选项 __thread 用__thread修饰这个全局变量即可。运行后主线程和新线程gval的地址也不一样了。这种情况叫线程的局部存储原始代码里只看到一个gval但是他们用的是各自的gval。 这种情况只在Linux中有效。__thread只能用来修饰内置类型 线程简单封装
#pragma once
#include iostream
#include string
#include pthread.hnamespace ThreadMoudle
{// 线程要执行的方法后面我们随时调整typedef void (*func_t)(const std::string name); // 函数指针类型class Thread{public:void Excute(){std::cout _name is running std::endl;_isrunning true;_func(_name);_isrunning false;}public:Thread(const std::string name, func_t func):_name(name), _func(func){std::cout create name done std::endl;}static void *ThreadRoutine(void *args) // 变成static内部没有this指针pthread_create就能匹配上了{Thread *self static_castThread*(args); // 获得了当前对象self-Excute();return nullptr;}bool Start(){int n ::pthread_create(_tid, nullptr, ThreadRoutine, this);//传this线程函数才能拿到_func方法if(n ! 0) return false;return true;}std::string Status(){if(_isrunning) return running;else return sleep;}void Stop(){if(_isrunning){::pthread_cancel(_tid);_isrunning false;std::cout _name Stop std::endl;}}void Join(){::pthread_join(_tid, nullptr);std::cout _name Joined std::endl;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;bool _isrunning;func_t _func; // 线程要执行的回调函数};
} // namespace ThreadModle 线程互斥
进程线程间的互斥相关背景概念 多线程执行流共享的资源叫做临界资源临界区每个线程内部访问临界资源的代码就叫做临界区互斥任一时刻互斥保证只有一个线程可以进入临界区去访问临界资源通常对临界资源起保护作用原子性不会被任何调度机制所打断的操作该操作只有两个状态要么完成要么未完成 下面通过抢票代码演示
void route(const std::string name)
{while(true){if(tickets 0){// 抢票过程usleep(1000); // 1ms - 抢票花费的时间printf(who: %s, get a ticket: %d\n, name.c_str(), tickets);tickets--;}else{break;}}
}int main()
{Thread t1(thread-1, route);Thread t2(thread-2, route);Thread t3(thread-3, route);Thread t4(thread-4, route);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();
}
线程就用我们前面封装的线程。每抢到一张票就--直到没票为止。 由上面结果可知抢票抢到负数去了。因此多线程并发访问公共资源时可能会引发异常。 但部分情况线程所使用的变量都是局部变量变量的地址空间在线程栈空间内这种情况变量归属于单个线程其他线程无法获得这种变量但有时候很多变量需要在线程间共享这种变量称之为共享变量通过数据的共享完成线程之间的交互多个线程并发的一些操作就会带来一些问题 为什么会出现抢到负数的问题呢 判断的过程就是计算是由CPU来做的判断的大致过程 数据要先从内存移到对应的寄存器当中然后再进行逻辑判断然后才能得到结果最后CPU再决定到底是进行if操作还是else操作 之前讲过CPU内的寄存器只有一套但是寄存器内的数据可以有多套 这里我们有四个线程进行抢票如果一个线程在判断到一半被切换了他需要把寄存器中的数值带走等到被唤醒的时候再把数值恢复过来 比如假设票只剩一张了线程a已经在判断完了当他准备抢票的时候他被切换了。此时他就把上下文数据保存。线程b被唤醒了他也来进行票数判断因为刚刚线程a还没来得及抢票票数没--所以线程b也判断成功他也要抢票。线程a此时被唤醒就往后执行代码进行抢票然后--票数就变成0。线程b又被唤醒票数又--就变成负数了。 如何解决上面的问题呢加锁
锁 pthread_mutex_t是互斥锁类型 互斥锁在任何时刻只允许一个线程进行资源访问。 有了锁我们往往需要初始化和销毁锁初始化有两种做法 如果定义的是全局或者静态的锁可以只使用pthread_mutex_t 锁的名字 PTHREAD_MUTEX_INITIALIZER 如果定义的这把锁是动态申请的比如new或栈上开辟的必须使用pthread_mutex_init函数来进行初始化。参数1就是你自己定义的锁参数2是属性直接设为nullptr即可 用完锁后还需要销毁用pthread_mutex_destroy函数参数是锁的地址。如果锁是静态或者全局的我们不需要destroy全局的或者静态的变量会随着进程的运行而一直存在进程结束他也就自动释放了。初始化和销毁的返回值成功返回0失败返回-1。 一旦有了锁我们就需要对临界区进行保护 就需要加锁和解锁。要对某个区域加锁就要调用pthread_mutex_lock函数来加锁参数就是你定义的锁。要解锁就用pthread_mutex_unlock函数。 lock的情况 互斥量处于未锁状态该函数会将互斥量锁定同时返回成功发起函数调用时其他线程已经锁定互斥量或者存在其他线程同时申请互斥量但没有竞争到互斥量 那么pthread_ lock调用会陷入阻塞(执行流被挂起)等待互斥量解锁。 所谓对临界区资源进行保护本质是对临界区代码进行保护。 把前面抢票的代码改一下加锁。运行结果发现不会再抢到负数了而且运行时间明显比之前要长。 加锁的范围粒度要尽量小即临界区尽量小。所有线程申请锁前提是所有线程都看得到这把锁因此锁本身也是共享资源。所以加锁的过程必须是原子的原子性要么不做要么完成了。没有中间状态就是原子性。如果线程申请锁失败了线程就要被阻塞如果线程申请锁成功了就继续往后运行执行临界区代码。 在执行临界区代码期间线程可以被切换。假设线程1正在执行临界区代码此时线程被切换了其他线程也无法进入临界区因为线程1并没有释放锁。 结论我这个线程访问临界区对其他线程来说是原子的。因为对于其他线程我要么没有申请锁要么释放了锁这样对他们才有意义。 锁简单封装 因为是临时对象在循环结束后会自动调用析构函数销毁。 互斥量实现原理 经过上面的例子大家已经意识到单纯的 i 或者 i 都不是原子的有可能会有数据一致性问题为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 Linux线程同步 同步概念与竞态条件
同步在保证数据安全的前提下让线程能够按照某种特定的顺序访问临界资源从而有效避免饥饿问题叫做同步竞态条件因为时序问题而导致程序异常我们称之为竞态条件。 如上图线程2一直抢到票其他线程一直抢不到这时候就需要线程同步 条件变量
当一个线程互斥地访问某个变量时它可能发现在其它线程改变状态之前它什么也做不了。例如一个线程访问队列时发现队列为空它只能等待直到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。 举例有A,B,C三个人一个盘子。B拿出苹果放到盘子上另外两人就可以到盘子上拿。为了在放苹果的时候其他人不能来拿就要加锁盘子就是临界区。因为另外两人想拿苹果就一直申请锁导致B放不了苹果。此时就需要一个铃铛。AC两人在外面排队当B放好苹果后就摇铃铛此时A和C就会根据排队的顺序依次进去拿苹果。 上面的铃铛就是条件变量人就是线程。摇铃铛后可以规定是唤醒一个线程还是唤醒全部。 认识接口 条件变量是 pthread_cond_t 的数据类型。它的使用跟前面互斥锁一样可以定义成局部或者全局的。如果是全局或者静态的可以直接使用 PTHREAD_COND_INITIALIZER 初始化。 如果是局部的就用pthread_cond_init 函数初始化使用完了就destroy销毁掉。 线程条件不满足时线程就要等待要在指定的条件变量上等待。 cond要在这个条件变量上等待 等待完成后就要进行唤醒。 pthread_cond_signal 表示唤醒一个线程。 pthread_cond_broadcast 表示唤醒所有线程。 生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯而通过阻塞队列来进行通讯所以生产者生产完数据之后不用等待消费者处理直接扔给阻塞队列消费者不找生产者要数据而是直接从阻塞队列里取阻塞队列就相当于一个缓冲区平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
生产者消费者模型优点
解耦支持并发支持忙闲不均 为了方便记忆这里有一个“321”原则 一个交易场所(一段内存空间) 两种角色(生产、消费角色) 三种关系(生产和生产、消费和消费 、生产和消费)前两种是互斥关系最后一种是互斥和同步的关系 基于BlockingQueue的生产者消费者模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于当队列为空时从队列获取元素的操作将会被阻塞直到队列中被放入了元素当队列满时往队列里存放元素的操作也会被阻塞直到有元素被从队列中取出 BlockQueue.hpp
#pragma once#include iostream
#include string
#include queue
#include pthread.hconst static int defaultcap 5;template typename T
class BlockQueue
{
private:bool IsFull(){return _block_queue.size() _max_cap;}bool IsEmpty(){return _block_queue.empty();}public:BlockQueue(int cap defaultcap) : _max_cap(cap){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_p_cond, nullptr);pthread_cond_init(_c_cond, nullptr);}// 假设2个消费者void Pop(T *out){pthread_mutex_lock(_mutex);while (IsEmpty()) // while可以保证代码的鲁棒性(健壮性)不用if因为如果只有一个生产品{ //有两个消费者一次性唤醒两个消费者的话他们会竞争锁其中一个拿完东西后另一个才能重新拿到锁//另一个拿到锁后不用if的话就跳过判断了此时队列是空的就会异常所以用while// 添加尚未满足但是线程被异常唤醒的情况叫做伪唤醒pthread_cond_wait(_c_cond, _mutex); // 两个消费者都在这里等待了}// 1. 没有空 || 2. 被唤醒了*out _block_queue.front();_block_queue.pop();// if(_block_queue.size() hight_water)// pthread_cond_signal(_p_cond);pthread_mutex_unlock(_mutex);pthread_cond_signal(_p_cond);}// 一个生产者void Equeue(const T in){pthread_mutex_lock(_mutex);while (IsFull()) {// 满了生产者不能生产必须等待// 可是在临界区里面啊// 被调用的时候除了让自己继续排队等待还会自己释放传入的锁// 函数返回的时候不就还在临界区了// 返回时必须先参与锁的竞争重新加上锁该函数才会返回pthread_cond_wait(_p_cond, _mutex);}// 1. 没有满 || 2. 被唤醒了_block_queue.push(in); // 生产到阻塞队列pthread_mutex_unlock(_mutex);// 让消费者消费pthread_cond_signal(_c_cond);//解锁和唤醒的顺序可以交换不影响}~BlockQueue(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_p_cond);pthread_cond_destroy(_c_cond);}private:std::queueT _block_queue; // 临界资源int _max_cap;pthread_mutex_t _mutex;pthread_cond_t _p_cond; // 生产者条件变量pthread_cond_t _c_cond; // 消费者条件变量// int low_water _max_cap/3// int hight_water _max_cap/3*2
};
Main.cc
#include BlockQueue.hpp
#include Task.hpp
#include pthread.h
#include ctime
#include unistd.hvoid *Consumer(void *args)
{BlockQueuetask_t *bq static_castBlockQueuetask_t *(args);while(true){// 1. 获取数据task_t t;bq-Pop(t);// 2. 处理数据// t.Excute();t();// std::cout Consumer - t.result() std::endl;}
}void *Productor(void *args)
{srand(time(nullptr) ^ getpid());BlockQueuetask_t *bq static_castBlockQueuetask_t *(args);while(true){// 1. 构建数据/任务// int x rand() % 10 1; // [1, 10]// usleep(x * 1000);// int y rand() % 10 1; // [1, 10]// Task t(x, y);// 2. 生产数据bq-Equeue(Download);std::cout Productor - Download std::endl;sleep(1);}
}int main()
{BlockQueuetask_t *bq new BlockQueuetask_t();pthread_t c1,c2, p1,p2,p3;pthread_create(c1, nullptr, Consumer, bq);pthread_create(c2, nullptr, Consumer, bq);pthread_create(p1, nullptr, Productor, bq);pthread_create(p2, nullptr, Productor, bq);pthread_create(p3, nullptr, Productor, bq);pthread_join(c1, nullptr);pthread_join(c2, nullptr);pthread_join(p1, nullptr);pthread_join(p2, nullptr);pthread_join(p3, nullptr);return 0;
}
Task.hpp
#pragma once#includeiostream
#includefunctional// typedef std::functionvoid() task_t;
using task_t std::functionvoid();void Download()
{std::cout 我是一个下载的任务 std::endl;
}// // 要做加法
// class Task
// {
// public:
// Task()
// {
// }
// Task(int x, int y) : _x(x), _y(y)
// {
// }
// void Excute()
// {
// _result _x _y;
// }
// void operator ()()
// {
// Excute();
// }
// std::string debug()
// {
// std::string msg std::to_string(_x) std::to_string(_y) ?;
// return msg;
// }
// std::string result()
// {
// std::string msg std::to_string(_x) std::to_string(_y) std::to_string(_result);
// return msg;
// }// private:
// int _x;
// int _y;
// int _result;
// }; 多线程生产者消费者模型高效是因为一个生产者在生产任务的时候其他生产者在构建任务一个消费者在获取任务的时候其他消费者在处理任务。 为什么线程在等待的时候都是在加锁和解锁之间等待 答无论是生产者还是消费者都必须先检查资源的状态。检查就是要访问所以检查之前就要加锁等待必须在临界区里进行等待因为判断结果是在临界区里的。 最后
十分感谢你可以耐着性子把它读完和我可以坚持写到这里送几句话对你也对我
1.一个冷知识 屏蔽力是一个人最顶级的能力任何消耗你的人和事多看一眼都是你的不对。
2.你不用变得很外向内向挺好的但需要你发言的时候一定要勇敢。 正所谓君子可内敛不可懦弱面不公可起而论之。
3.成年人的世界只筛选不教育。
4.自律不是6点起床7点准时学习而是不管别人怎么说怎么看你也会坚持去做绝不打乱自己的节奏是一种自我的恒心。
5.你开始炫耀自己往往都是灾难的开始就像老子在《道德经》里写到光而不耀静水流深。
最后如果觉得我写的还不错请不要忘记点赞✌收藏✌加关注✌哦(ω)
愿我们一起加油奔向更美好的未来愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油为自己点赞