当前位置: 首页 > news >正文

做食物网站应该考虑些什么意思怎么快速优化网站

做食物网站应该考虑些什么意思,怎么快速优化网站,重庆网站推广系统,王野甲壳虫Linux系统#xff1a;线程概念 线程控制 线程概念轻量级进程 LWP页表 线程控制POSIX 线程库 - ptherad线程创建pthread_createpthread_self 线程退出pthread_exitpthread_cancelpthread_joinpthread_detach 线程架构线程与地址空间线程与pthread动态库 线程的优缺点 线程… Linux系统线程概念 线程控制 线程概念轻量级进程 LWP页表 线程控制POSIX 线程库 - ptherad线程创建pthread_createpthread_self 线程退出pthread_exitpthread_cancelpthread_joinpthread_detach 线程架构线程与地址空间线程与pthread动态库 线程的优缺点 线程概念 假设某个进程要在输出1 - 10的同时让另外一个执行流去输出11 - 20应该怎么办 如果从进程的角度出发我们可以创建一个子进程让父子进程同时跑比如这样 int main() {pid_t id fork();//子进程if (id 0){for (int i 1; i 10; i){cout i endl;sleep(1);}exit(0);}//父进程for (int i 11; i 20; i){cout i endl;sleep(1);}return 0; }输出结果 这样确实可以做到让两个执行流同时运行从而提高效率但是代价太大了。这种方式是通过创建一个子进程实现的而一个进程需要额外创建PCB进程地址空间页表等等。 在CPU调度进程的时候本质是在调度进程的PCB把进程的PCB放到运行队列中然后CPU依次执行队列中的进程。也就是说如果我们想要让一个进程被CPU多次执行从而让一个进程有多个执行流只需要创建更多的PCB就可以了。在没学习到线程前我们认为一个进程只有一个PCB实则不然一个进程可以有多个PCB仅对Linux而言。 而对于同一个进程的每一个PCB都算作一个线程。 线程是进程内部的一个执行分支是CPU调度的基本单位 每个线程都会拿到同一个进程的不同部分代码从而让多个区域的代码一起执行提高进程的效率。 我们再辨析一下进程与线程的关系 进程 内核数据结构 代码和数据 上图中多个PCB进程地址空间页表等都属于进程的内核数据结构因此这些所有内容加起来才算做进程。 进程是承担系统资源分配的基本实体 因为多个线程是共享地址空间页表等等所以一个线程不可能去承担系统的资源分配反而来说线程是被分配资源的。操作系统分配资源时以进程为单位当一个进程拿到资源后再去分配给不同的线程。 轻量级进程 LWP 其实我刚刚的所有描述都是针对Linux而言的。大部分操作系统的处理线程的方式并不是直接创建多个PCB而是额外设计了一个TCB (Thread Contrl Block) 多个TCB分别控制不同部分的代码CPU调度线程时也去调度TCB。比如主流的WindowsMacOS等操作系统都是这样做的。 但是Linux认为PCB就已经可以被CPU调度了进程调度已经有一套很完善的体系了如果再额外给线程设计一套线程的调度解决方案未免太多余了。因此Linux中没有设计TCB这样的结构而是直接复用PCB来实现线程。 在Linux中一个进程可以有多个PCB当PCB的数目为1那么这个PCB可以代表一个进程如果PCB数目有多个那么这个PCB代表一个进程的多个线程。 因此在Linux中没有真正的线程一个执行流由一个PCB维护。一个执行流既有可能表示一个进程也有可能表示一个线程。Linux把这种介于线程与进程之间的状态称为轻量级进程 LWP (Light Weight Process)。 Linux中所有对线程的操作本质都是对轻量级进程的操作 接下来在一个进程内部创建两个线程观察一些现象。如何创建线程在本博客的线程控制部分会讲解现在大家只需要观察现象知道我的一个进程内部有两个线程即可。 上图中为两个线程同时输出第一个线程输出I am thread - 1第二个线程输出I am thread - 2。 我们先通过 ps -ajx观察一下进程test.exe的状态 看可以看到只有一个进程PID 141776也就是这两个线程同属于一个进程。 如果想要观察线程需要指令ps -aL 可以看到确实是有两个叫做test.exe的线程的它们的PID都是141776但是它们的LWP不同。一个LWP 141776另外一个LWP 141777而LWP就是我们刚刚说的轻量级进程。 另外的你会发现第一个线程的PID LWP 141776说明这个线程是主线程其余的所有线程都是这个主线程创造的。 页表 那么进程是如何实现把资源分配给多个线程的呢这就要谈一谈页表了。 先看一看内存与磁盘是如何管理的 不论是内存还是磁盘都被划分为了以4kb为单位的数据块一个数据块可以被称为页框 / 页帧。 操作系统管理内存或者管理磁盘都是以4kb为基本单位的。比如把磁盘中的数据加载到内存中就是以4kb为基本单位进行拷贝。 页框是被struct page管理的Linxu 2.6.10中struct page源码如下 struct page {page_flags_t flags; atomic_t _count; atomic_t _mapcount; unsigned long private; struct address_space *mapping;pgoff_t index;struct list_head lru; #if defined(WANT_PAGE_VIRTUAL)void *virtual; #endif };操作系统想要管理所有的页框只需要创建一个数组数组的元素类型是struct page。此时操作系统对内存或磁盘的管理就变成了对数组的增删查改。而且从上方的struct page源码中可以发现它是不存储页框的起始地址和终止地址的因为可以通过下标计算出起始地址起始地址 4kb就可以求出终止地址。 那么这个页框和页表有什么关系呢 我们以32位操作系统为例页表的结构如下 页表的任务是把虚拟地址解析为物理地址当传入一个虚拟地址页表就要对其解析。一个32位的地址会被分为三部分第一部分是前10位第二部分是中间10位第三部分是末尾12位。 第一部分就是上图中的深蓝色部分其由页目录进行解析。 2 10 1024 {\color{Red} 2 ^ {10} 1024} 2101024即前10位地址有1024种可能而页目录就是一个长度为1024的数组。解析地址时先通过前10位在页目录中找到对应的下标。每个页目录的元素指向一个页表。 第二部分是上图中的黄色部分其由页表进行解析同样的 2 10 1024 {\color{Red} 2 ^ {10} 1024} 2101024即中间10位地址也1024种可能所以每个页表的长度也是1024。解析中间10位时在页表中找到对应的下标从而找到对应的内存。 第三部分时上图中的绿色部分。还记得吗一个数据块的大小是4 kb这是内存管理的基本单位。而 2 12 b y t e 4 k b {\color{Red} 2 ^ {12} byte 4 kb} 212byte4kb而第三部分就是12位因此第三部分也叫做页内偏移通过前两个部分我们已经可以锁定到内存中的一个页框了而第三部分存储的是物理地址相对于页框起始地址的偏移量此时就可以根据起始地址 偏移量来确定一个地址。 以上就是页表解析地址的全过程。 那么这和线程的资源分配有什么关系呢 我们可以看到一个页目录把整个页表划分为了1024部分。 给不同线程分配进程不同的区域本质就是让不同进程看到页表的不同子集 线程控制 POSIX 线程库 - ptherad 讲完基本概念后我们再看看如何控制线程。Linux控制线程是通过原生线程库pthread的。 Linux中本质上是没有线程的而是通过轻量级进程来模拟线程。因此 Linux 没有线程相关的系统调用接口而是轻量级进程的系统调用接口。为了让用户感觉自己在操控线程因此所有Linux系统都会必须配套一个 原生线程库 pthread将轻量级进程的系统调用封装为线程的操控让用户感觉自己在操控线程。 之所以叫做原生线程库就是因为所有Linux系统必须配备这个库是原生的。因为库属于用户操作接口的范围所以Linux的线程也叫做用户级线程。 在使用gcc / g编译时要带上选项-l pthread来引入原生线程库 例如 g -o test.exe test.cpp -l pthread那么简单了解什么是线程库后接下来就要讲解线程相关接口了。 在讲解接口前先铺垫一个概念TID所有线程库对线程的操作都是基于TID的。这个TID用于标识一个唯一的线程。TID与LWP不是一个东西不要搞混。 TID的数据类型是pthread_t。 线程创建 pthread_create ptherad_create函数用于创建一个线程需要头文件pthread.h函数原型如下 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);参数 thread输出型参数输出创建出来的线程的TIDattr设置线程的状态这个不用管设为nullptr即可start_routine类型为void* (*)(void*)线程执行的函数的函数指针arg用于给新的线程传递参数也就是给函数start_routine传参该函数的第一个参数为void*类型arg也是void* 返回值 如果创建成功返回0如果创建失败返回错误码 示例 void *threadRun(void *args) {string name (char *)args;while (true){cout name endl;sleep(1);}return nullptr; }int main() {pthread_t tid;pthread_create(tid, nullptr, threadRun, (void *)thread - 1);while (true){cout thread - main endl;sleep(1);}return 0; }以上代码中先创建一个pthread_t类型的tid随后通过pthread_create创建一个线程。 第一个参数tid即创建线程后把该线程的TID输出到变量tid中第二个参数nullptr不用管第三个参数threadRun即创建出来的线程去执行这个函数第四个参数(void *)thread - 1即函数threadRun的第一个参数传入一个字符串由于参数类型是void*要进行一次类型转换。 随后主线程循环输出thread - main创建的线程把参数args提取出来变成stringstring name (char *)args;再输出这个string。 输出结果 现在就有两个线程同时在跑了而且我们成功把字符串thread - 1通过参数传给了新创建的线程。 pthread_self ptherad_self函数用于得到当前线程的TID需要头文件pthread.h函数原型如下 pthread_t pthread_self(void);返回值当前线程的TID。 线程退出 讲解线程退出的相关接口前我们来看一个实验 如果某个线程调用exit接口会发生什么 代码 void *threadRun(void *args) {string name (char *)args;int cnt 3;while (cnt--){cout name endl;sleep(1);}exit(0);return nullptr; }int main() {pthread_t tid;pthread_create(tid, nullptr, threadRun, (void *)thread - 1);while (true){cout thread - main endl;sleep(1);}return 0; }以上代码十分简单就是在刚才的代码中多加了一句exit而已。主线程创建完线程后死循环输出thread - main。而创建出来的线程在输出三次thread - 1后通过exit退出。 按理来说创建出来的线程通过exit退出后此时就只剩主线程了于是一直循环输出thread - main。所以预测现象为输出三次thread - 1后剩下的全是thread - main。 输出结果 奇怪的现象发生了当thread - 1通过exit退出后主线程也退出了。 exit接口的作用是终止整个进程任意一个线程调用该接口所有线程都会退出 因此我们不能通过exit来退出一个线程。 pthread_exit ptherad_exit函数用于退出当前线程需要头文件pthread.h函数原型如下 void pthread_exit(void *retval);在pthread_create时线程执行的函数类型就是void* (*)(void*)也就是说线程调用的函数返回值是void*。退出线程pthread_exit时第一个参数retval就是用于指定这个返回值的。 pthread_cancel ptherad_cancel函数用于在主线程中指定退出一个线程需要头文件pthread.h函数原型如下 int pthread_cancel(pthread_t thread);参数thread是要杀掉的线程的TID pthread_join 线程和进程一样也有等待的机制用于获取线程的退出信息这个过程叫做线程等待而线程等待就是通过pthread_join完成的。 pthread_join函数用于等待一个线程需要头文件pthread.h函数原型如下 int pthread_join(pthread_t thread, void **retval);参数 thread等待的线程的TIDretval输出型参数线程退出后该参数会接收到线程的函数返回值 返回值 等待成功返回0等待失败返回错误码 ptherad_join会进行阻塞式等待。 示例 void* threadRun(void* args) {string name (char*)args;int cnt 3;while (cnt--){cout name endl;sleep(1);}return (void*)12345; }int main() {pthread_t tid;pthread_create(tid, nullptr, threadRun, (void*)thread - 1);void* ret;pthread_join(tid, ret);cout return value (long long) ret endl;return 0; }以上代码中创建的线程输出三次thread - 1后退出返回值为数字12345。主线程中通过join等待这个线程并把返回值保存到变量ret中。随后输出这个结果因为ret是指针输出时先转为long long再输出。 输出结果 可以看到主线程成功通过pthread_join拿到了线程的返回值。 如果我们通过pthread_exit终止线程该函数的第一个参数就是函数返回值那么pthread_join得到的返回值就是pthread_exit的参数。 但是如果通过pthread_cancel终止线程由于该函数没有指定线程的返回值此时pthread_join得到的返回值是固定值PTHREAD_CANCELED其值为void*的-1。 pthread_detach 线程都是要被释放的如果一个线程退出后没有被pthread_join就会造成内存泄漏。但是如果你真的不希望去回收一个线程你可以进行线程分离被分离的线程退出后会自己回收自己。 pthread_detach函数用于分离一个线程需要头文件pthread.h函数原型如下 int pthread_detach(pthread_t thread);参数thread是被分离的线程的TID 返回值 分离成功返回0分离失败返回错误码 一个线程被分离后不允许再被pthread_join等待等待会发生错误。 pthread_detach既可以在主线程中对创建出来的线程使用也可以线程自己对自己使用。 我们以后者作为示例 void *threadRun(void *args) {pthread_detach(pthread_self());while(true) ;return (void*)12345; }int main() {pthread_t tid;pthread_create(tid, nullptr, threadRun, (void *)thread - 1);cout join... endl;int ret pthread_join(tid, nullptr);cout ret : strerror(ret) endl;return 0; }以上代码中在线程的函数中自己分离自己pthread_detach(pthread_self())随后创建的线程陷入死循环。 主线程中强行pthread_join这个已经分离的函数并输出错误码。 输出结果 可以看到由于等待了一个已经分离的线程此时pthread_join的返回值就是22号错误Incalid argument 线程架构 线程与地址空间 依然是这一张图我们之前一直没有讨论一个问题那就是所有的线程共享一个进程地址空间。这意味着多个线程是可以访问同一个变量的比如说一个全局变量g_val我们可以在任意一个线程中访问它一个线程修改了这个值其余的线程的g_val也会受到影响。 示例 int g_val 5;void* threadRun(void* args) {cout g_val g_val g_val g_val endl;return nullptr; }int main() {pthread_t tid;pthread_create(tid, nullptr, threadRun, nullptr);sleep(1);cout g_val g_val g_val g_val endl;return 0; }以上代码中先创建了一个全局变量g_val随后创建一个线程。 主线程与被创建的线程都输出g_val的值与g_val的地址。 输出结果 两个线程的g_val不论是值还是地址都是一模一样的说明就是同一个g_val。 如果你希望对于一个变量每个线程都维护一份互不影响此时可以使用线程局部存储。 只需要在变量前面使用__thread注意前面有两条下划线修饰即可比如这样 __thread int g_val 5;再次输出 这次两个线程的g_val地址就不同了。 当然线程之间不是啥都共享的也有各自独立的部分 线程PID一组寄存器 / 硬件上下文因为线程是调度的基本单位线程调度是需要上下文来记录自己的调度相关信息的这个必须每个线程各自一份栈帧也是因为线程是调度的基本单位每个线程都在执行自己的函数栈这个栈也是各自独立errno线程之间的错误码各自独立信号屏蔽字当一个进程收到信号此时所有线程都会收到信号但是每个线程的处理方式可以不同就是通过每个线程独自维护一份block达到的调度优先级同理因为线程是调度的基本单位线程之间可以有不同的优先级 线程与pthread动态库 我先前说过线程的控制是通过pthread库实现的毫无疑问pthread是一个动态库。线程需要被结构体描述同时再被数据结构组织这样才好管理一群线程这个任务是pthread库完成的。 如下图 在pthread库中通过结构体struct pthread来管理一个线程在这给结构体中会存储很多线程之间独立的部分。我们刚讲过的线程局部存储栈帧TID这几个部分就是保存在struct pthread中的。 还记得我们在说过TID是一个线程的唯一标识符吗我们来输出一下这个TID试试 int main() {cout pthread_self() endl;return 0; }直接通过pthread_self拿到自己的TID然后输出。 输出结果 可以发现这是一个很大很大的数字这是为啥 TID就是线程的struct pthread在内存中的物理地址 你不妨想想为什么所有对线程的操作中基本都带上了参数TID因为通过TID线程可以直接找到对应的物理地址从而访问线程的更多信息。 在Linux中标识一个轻量级进程是通过LWP完成的在struct pthread内部一定有一个成员是该线程对应的轻量级进程的LWP进而完成用户操作线程到库操作轻量级进程的转换。 线程的优缺点 线程有如下优点 创建一个新线程的代价要比创建一个新进程小得多 创建一个线程只需要额外创建一个PCB进程地址空间页表等都和别的线程共用因此创建线程代价很小 与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多 由于线程共用进程地址空间和页表在CPU切换调度的PCB时如果两个PCB属于同一个进程的不同线程那么它们的进程地址空间和页表都不用更新因为它们共用此时CPU就可以少加载很多数据。 在等待慢速I/O操作结束的同时程序可执行其他的计算任务 一般来说I/O是很慢的操作如果主线程一直等待I/O效率就会很低下。这种需要阻塞等待的任务建议创建其它线程让其他线程去做。这样I/O对程序的运行效率影响就低很多因为等待I/O的同时主线程可以去做其他任务。 计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现 所谓计算密集型应用就是该程序主要功能是利用CPU进行计算。此时线程个数不建议太多因为线程的调度也是需要消耗CPU资源的此时最好让CPU多执行计算而不是去频繁调度。 I/O密集型应用为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作。 所谓I/O密集型应用就是说程序大部分时间都在I/O此时建议多创建一些线程让线程去I/O。 线程缺点如下 线程的频繁调度也要消耗资源可能会和计算争夺CPU资源导致计算效率降低比如计算密集型应用就不太适合创建太多线程程序健壮性降低因为多线程的代码很难维护不好控制
http://www.laogonggong.com/news/110946.html

相关文章:

  • 做网站ps文字有锯齿京东联盟怎么做网站
  • 织梦网站广告网站建设 学生作业
  • 东莞市建设安监局网站专业的网站开发服务商
  • 标书制作文员主要干什么重庆seo排名电话
  • ps常用素材网站有哪些wordpress 付费剧集网站
  • 现在哪个网站可以做外贸相同网站名
  • 下列关于网站开发国内课程网站建设现状
  • 怎么做内网网站软件开发流程ppt
  • 微信公众号绑定网站如何制作漂亮的微信公众号文章
  • 在线书店网站怎么做网站维护 一年
  • 佛山企业设计网站建设广东嵘通建设
  • 网站建设于朦胧郑州网站建设微信小程序
  • 公司网站建设费如何出账网站推广策划
  • 网站弄好了怎么推广wordpress 插件调用文章
  • 中国住房和城乡建设网站网站域名查询地址
  • 网站建设中的接口网站建设就业
  • 米拓网站建设步骤做分析图地图网站
  • 珠海中小企业网站建设百度指数排名
  • 中国十大搜索引擎排名最新百度seo关键词优化排名
  • 常用的网站流量统计软件有哪些海南省建设工程质量监督网站
  • 什么网站做的好网络媒体广告公司
  • 怎样做网站导航界面最近重大新闻事件2021
  • 网站地图做几个品牌高端网站制作官网
  • 学设计的网站推荐网站关键词用热门的还是冷门
  • 商城网站后台管理系统男女做爰网站19
  • 大数据营销名词解释快速优化排名公司推荐
  • html5 爱情网站模板深圳非凡网站建设公司
  • 网站开发销售话术ngo网页模板下载
  • 最新网站域名ip查询app开发公司排名 上市企业
  • asp.net 网站开发视频教程北京网站建设怎么样天