网站建设哪里有,网站网页不对称,游戏开发软件免费下载,wordpress 访问记录插件文章目录线程的基础概念线程控制内核LWP和线程ID的关系线程的基础概念
一般教材对线程描述是#xff1a;是在进程内部运行的一个分支#xff08;执行流#xff09;#xff0c;属于进程的一部分#xff0c;粒度要比进程更加细和轻量化
一个进程中是可能存在多个线程…
文章目录线程的基础概念线程控制内核LWP和线程ID的关系线程的基础概念
一般教材对线程描述是是在进程内部运行的一个分支执行流属于进程的一部分粒度要比进程更加细和轻量化
一个进程中是可能存在多个线程的可能是11也可能是1n所以线程也应由OS管理管理就离不开“先描述再组织”所以线程也应该类似PCB一样有线程控制块TCB但Linux中没有专门为线程设计的TCB而是用进程PCB来模拟线程
通过某些函数方法可以在同一个进程中只创建task_struct让这些PCB共享同一个地址空间把当前进程的资源代码数据划分成若干份让每个PCB使用执行一部分。
站在CPU的角度CPU只看PCB不关心是否共享一份地址空间此时CPU看到的PCB就是一个需要被调度的执行流 Linux中是用进程来模拟线程的所以也叫轻量级进程站在用户角度是线程不用单独为线程设计算法可直接用进程的一套相关方法所以不用维护复杂的进程和线程的关系。OS只需要聚焦在线程间的资源分配上就可以了所以CPU是只看PCB不区分线程和进程。
进程与线程的区别 进程具有独立性可以有部分资源共享基于管道、ipc资源下 线程共享进程的数据但是也有属于自己的数据 进程是资源分配的基本单位承担系统资源分配的基本实体 线程是CPU调度的基本单位承担进程资源一部分的基本实体
线程的优点 创建一个新的线程的代价比创建一个进程小得多。创建一个线程虽然也需要创建数据结构但是并不需要重新开辟资源只需要将进程的部分资源分配给线程。创建一个进程不仅需要创建大量数据结构还需要重新创建资源。 与进程之间的切换相比线程之间的切换需要操作系统做的工作少。线程只是进程的部分资源切换的资源少。 线程占用的资源比进程少 能充分利用多处理器的可并行数量 在等待慢速的I/O任务结束的同时程序可以执行其它的计算任务 计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现。 I/O密集型应用为了提高性能将I/O操作重叠线程可以同时等待不同的I/O操作。I/O操作是与外设交互数据会很慢。
线程的缺点 性能缺失 一个处理器只能处理一个线程如果线程数比可用处理器数多会有较大的性能损失会增加额外的同步和CPU调度的开销而资源却是不变的 健壮性降低 编写多线程时, 可能因为共享的变量, 导致一个线程修改此变量, 影响另外一个线程。(线程不是对立的所以线程之间缺乏保护) 多线程之间的变量是同一个变量多进程之间变量不是同一个变量一开始是共享父进程的但在改变时发生写时拷贝 缺乏访问的控制 进程时访问控制的基本粒度在一个线程中调用某些OS函数会对整个进程造成影响 编程难度相对高
线程异常
单个线程如果出现除零野指针问题导致线程崩溃进程也会随着崩溃 线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程该进程内的所有线程也就随即退出
线程用途
合理的使用多线程能提高CPU密集型程序的执行效率合理的使用多线程能提高IO密集型程序的用户体验如生活中我们一边写代码一边下载开发工具就是 多线程运行的一种表现
线程与进程的关系 线程控制
因为是用进程模拟的线程所以Linux下不会给我们提供直接操作线程的接口而给线程提供的是在同一个地址空间创建PCB的方法、分配资源给指定的PCB的接口等等需要用户自己创建一些函数方法如创建线程、释放线程、等待线程等对用户不太友好。
所以一般我们在使用的线程相关函数方法都是由系统级别的工程师在用户层对Linux轻量级进程提供的方法进行封装打包成的库。
下面主要介绍pthread库
创建线程函数 threaad输出型参数会获取刚创建的线程IDattr传入的是线程的属性优先级、运行栈等等一般传入NULL交给OS默认处理。start_routine创建线程后所执行的方法arg一般会传给第三个参数的函数方法
#include iostream
#include string
#include pthread.h
#include unistd.hvoid* ThreadRun(void* args)
{std::string name (char*)args;while(true){std::cout 当前处于新线程新线程ID是 pthread_self() , args: name std::endl;sleep(1);}
}int main()
{pthread_t tid[5];for(int i 0;i 5;i)pthread_create(tid[i],nullptr,ThreadRun,(void*)new thread);while(true){std::cout 当前处于主线程,主线程ID是 pthread_self() std::endl;std::cout ################################################################### std::endl;for(size_t i 0;i 5;i)std::cout 创建的新线程[ i ]ID是 tid std::endl;std::cout ################################################################### std::endl;sleep(3);}return 0;
}犹豫是多线程打印会有乱码现象。
代码中用到的pthread_self()作用是在谁的线程下用就获取谁的线程 ID 可以看到的是他们的PID都是一样的所以线程是对进程的资源分配 LWP是内核提供的和打印出来的id是不一样的这里用的是原生线程库打出来的id是其实是进程地址号
线程等待
进程退出有三种方式
代码正常运行结果正确代码正常运行结果不对程序异常直接退出
前两点退出时不会产生信号但会产生退出码而程序异常会产生终止信号线程分配的资源是进程的一部分所以只要有一个线程执行的代码部分导致程序崩溃整个进程会直接退出并产生终止信号整个进程崩溃所有线程也就崩溃了。
一般而言线程也是需要等待的否则也会出现类似僵尸进程那样的问题已经退出的线程其空间没有被释放仍然在进程的地址空间内占用资源还得维护数据
线程等待只能是在代码正常运行的前提下等待而程序异常进程就会直接退出了OS会向进程发送退出信号此时与线程无关下图所示线程等待函数 thread需要等待的线程的ID retval输出型参数获取创建线程时指定的函数方法的返回值因为指定的函数方法返回值是指针返回所以这里是二级指针 pthread_join函数在等待时是阻塞式等待
#include iostream
#include string
#include pthread.h
#include unistd.hvoid* ThreadRun(void* args)
{sleep(3);return (void*)finish;
}int main()
{pthread_t tid;pthread_create(tid,nullptr,ThreadRun,(void*)new thread);void* status NULL;pthread_join(tid,status);std::cout (char*)status std::endl;return 0;
}线程退出 线程退出的方法有三种 函数中return main函数return代表主线程和进程退出 其他线程函数中return代表当前线程退出 通过pthread_exit函数终止线程 参数是要传入的退出状态
void* ThreadRun(void* args)
{std::string name (char*)args;std::cout name running. . . std::endl;sleep(3);pthread_exit((void*)123);std::cout exit fail std::endl;return (void*)finish;
}int main()
{pthread_t tid;pthread_create(tid,nullptr,ThreadRun,(void*)new thread);void* status NULL;pthread_join(tid,status);printf(%d\n,(int*)status);return 0;
}通过pthread_cancel函数取消线程 只需要传入要取消的线程的id即可 成功返回0失败返回-1 void* ThreadRun(void* args)
{std::string name (char*)args;std::cout name running. . . std::endl;sleep(10);return (void*)finish;
}int main()
{pthread_t tid;pthread_create(tid,nullptr,ThreadRun,(void*)new thread);sleep(1);std::cout cancel sub thread. . . std::endl;sleep(5);pthread_cancel(tid);void* status NULL;pthread_join(tid,status);printf(%d\n,(int*)status);return 0;
}取消后的线程退出码是 -1 代表是PTHREAD_ CANCELED的意思
注意不建议在子线程中取消主线程的做法这样会导致进入僵尸进程的状态。因为主线程会退出而没有资源可以等待子线程退出子线程就造成资源浪费了
void* ThreadRun(void* args)
{sleep(5);pthread_cancel((pthread_t)args);while(1){std::cout running. . . std::endl;sleep(1);}return (void*)finish;
}int main()
{pthread_t tid;pthread_t g_tid pthread_self();pthread_create(tid,nullptr,ThreadRun,(void*)g_tid);void* status NULL;pthread_join(tid,status);printf(%d\n,(int*)status);return 0;
} 如图所示进入了僵尸进程以及类似僵尸进程的线程状态主线程 defunct
线程分离
若不想阻塞式的等待线程退出可以使用线程分离函数分离之后的线程不需要被join运行完毕后会自动释放资源 传入要分离的线程id即可
void* ThreadRun(void* args)
{pthread_detach(pthread_self());std::cout running. . . std::endl;sleep(1);return (void*)finish;
}int main()
{pthread_t tid;pthread_create(tid,nullptr,ThreadRun,(void*)hello);sleep(3);void* status NULL;int ret pthread_join(tid,status);printf(ret:%d,status:%s\n,ret,(char*)status);return 0;
}只要线程分离了就不可在使用join去等待了会等待失败
内核LWP和线程ID的关系
我们使用的pthread原生线程库是存在磁盘当中的在使用时会被加载到内存当中通过页表映射到进程地址空间的共享区堆区和栈区之间的区域所有的进程都共用同一份库只是进程空间中进程自己认为独占这个库
而每个线程共享的是同一份进程地址空间但是每个线程也会有自己的私有数据部分如线程栈主线程栈是在栈区的、上下文数据、属性等等。这些线程私有的部分组成线程的数据块可以想象成类似于task_struct的线程struct数据块其实是存储在线程库中的通过页表映射到进程地址的共享区。
创建线程的时候用mmap系统调用从heap分配出来的
在使用pthread_create创建线程时线程库会生成一个ID号来表示新创建的线程这个ID由函数的第一个参数可以获取到而线程ID就是这些数据块在共享区映射的地址每个线程id就是每个线程的数据块在共享区的地址通过id号便能找到线程的所有数据 线程id就是线程存储在线程库中的数据通过内存映射到进程地址空的共享区的地址标号有了这个标号就能找到线程的所有的私有数据
LWP和线程id的关系是什么
LWP是在内核层面的线程id是在用户层面的。
前面说过在Linux中没有提供专门的线程方法使用进程模拟的线程所以Liunx中的线程其实是轻量级进程依旧用的task_struct所以CPU调度知认PCB那进程由PID线程也该有个数字来标识这些task_struct那这个标识就是LWP所以LWP实际上是供CPU调度使用的类似文件描述符一样
线程id是用户层的LWP是内核层的那id怎么知道他是对应哪个task_struct的呢所以线程的数据块一定也包含一个LWP形成1:1的关系也有1n即一个线程id包含多个LWP在用户层面看来是一个线程