赣州市城乡建设局官方网站,文创产品推广方案,怎样去各大网站做淘宝推广,青岛艺腾网站建设文章目录 ☀️一、深入理解页表☀️二、Linux线程概念#x1f33b;1.什么是线程#xff08;重点#xff09;⚡#xff08;1#xff09;线程的概念⚡#xff08;2#xff09;线程库初识 #x1f33b;2.线程的优点#x1f33b;3.线程的缺点#x1f33b;4.线程异常… 文章目录 ☀️一、深入理解页表☀️二、Linux线程概念1.什么是线程重点⚡1线程的概念⚡2线程库初识 2.线程的优点3.线程的缺点4.线程异常重点5.线程用途 ☀️三、Linux线程VS进程1.线程和进程2.线程共享 ☀️四、Linux线程控制1.POSIX线程库2.创建线程3.线程终止4.线程等待5.线程控制代码示例⚡1简单版重点⚡2复杂版 ☀️五、C多线程引入☀️结语 ☀️一、深入理解页表 知识点1 地址空间task_struct是进程能看到的资源窗口。页表决定进程真正拥有的资源情况。合理的对地址空间页表进行资源划分我们就可以对一个进程的所有资源进行分类。 知识点2 磁盘的数据储存单元为页帧4KB物理内存被划分为一个一个数据页/页框4KB进行管理磁盘可以将数据加载到内存。虚拟地址需要用32位比特位来表示设计者们有意的将其拆分为10、10、12的位数进行应用方便对内存空间的映射。页表分为页目录、页表项。页目录使用虚拟地址的前10位进行索引指向不同的页表项。页表项使用虚拟地址的中10位进行索引指向不同物理内存中的页框的起始地址。操作系统依据某一页框的起始地址以虚拟地址后12位作为偏移量找到该页框内某一具体物理地址。在读取或写入某个数据时可直接根据数据大小在某一具体物理地址直接向下读取即可。页表根据需要进行创建不发生映射关系时对应的数据页表不创建可大大减少资源占用。 ☀️二、Linux线程概念
1.什么是线程重点
⚡1线程的概念 在一个程序里的一个执行路线就叫做线程thread。更准确的定义是线程是“一个进程内部的控制序列”。 一个进程至少有一个执行线程可以有多个执行线程。 线程在进程内部运行本质是在进程地址空间内运行它拥有进程的一部分资源。 在Linux系统中在CPU眼中看到的PCB都要比传统的进程更加轻量化。 透过进程虚拟地址空间可以看到进程的大部分资源将进程资源合理分配给每个执行流就形成了线程执行流。 在Linux下每个线程共享一个地址空间它们使用 task_strcut类型的数据结构对虚拟内存进行管理进而对整个进程资源分配。 在window下有为线程专门设计的数据结构TCB。 Linux内核中有没有真正意义的线程呢没有Linux是用进程PCB来模拟线程的是一套完全属于自己的线程方案。站在CPU视角每一个PCB都可以将其称之为轻量级进程。Linux线程是CPU调度的基本单位而进程是承担系统资源分配的基本单位。进程用来申请整体资源线程用来伸手向进程要资源。Linux没有真正意义上的线程 - Linux无法直接向外部提供线程的系统调用接口而只能给我们提供创建轻量级进程的接口pthread库接口。好处简单维护成本低 - 可靠高效避免数据结构复杂化。
⚡2线程库初识 我们可以通过调用用户级线程库pthread库让库帮我们访问对应的系统调用接口创建轻量级进程也就是我们Linux下的线程。 任何一款Linux操作系统都会默认携带这个 pthread库这种我们将其称之为原生线程库。
mythread:mythread.ccg -o $ $^ -lpthread -stdc11 //-lpthread - 使用pthread库
.PHONY:clean
clean:rm -f mythread多线程程序运行起来之后 LWPlight weight process - 轻量级进程id。PID LWP该线程为主线程。PID ! LWP该线程为新线程。CPU调度的时候以LWP为标识符表示特定执行流。创建一个线程 - 创建PCB - 把对应的代码分配给它分配一个函数给它
2.线程的优点
创建一个新线程的代价要比创建一个新进程小得多。与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多。线程占用的资源要比进程少很多。能充分利用多处理器的可并行数量。在等待慢速I/O操作结束的同时程序可执行其他的计算任务。计算密集型应用加密、解密、算法为了能在多处理器系统上运行将计算分解到多个线程中实现。I/O密集型应用外设、网络拉取为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作。 重点与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多。1.进程切换页表 虚拟空间地址 切换PCB 切换上下文。2.线程切换PCB 切换上下文。 知识补充CPU内部会集成一个硬件cache它负责缓存常用的热点数据当线程切换时cache不用被切换CPU/寄存器 可以直接从该集成硬件中直接读取数据。当进程切换时cache需要被切换。 3.线程的缺点
性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多那么可能会有较大的性能损失这里的性能损失指的是增加了额外的同步和调度开销而可用的资源不变。 健壮性/鲁棒性 降低重点 1.编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。2.一个线程出异常会影响其他线程因为信号是发给进程整体的同一个进程内的不同线程共享同一个PID。 缺乏访问控制 进程是访问控制的基本粒度在一个线程中调用某些OS函数会对整个进程造成影响。 编程难度提高。 编写与调试一个多线程程序比单线程程序困难得多。 4.线程异常重点
单个线程如果出现除零野指针问题导致线程崩溃进程也会随着崩溃。线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程进程终止该进程内的所有线程也就随即退出。多线程 不等于 多进程父子进程子进程崩溃不一定会影响父进程运行。
5.线程用途 合理的使用多线程能提高CPU密集型程序的执行效率。 合理的使用多线程能提高IO密集型程序的用户体验如生活中我们一边写代码一边下载开发工具就是 多线程运行的一种表现。 ☀️三、Linux线程VS进程
1.线程和进程
进程是资源分配的基本单位线程是CPU调度的基本单位线程共享进程数据但也拥有自己的一部分数据: 线程ID一组寄存器上下文栈errno信号屏蔽字调度优先级 总结线程一旦被创建几乎所有资源都是被线程共享的。线程也有自己的私有资源1.PCB属性私有2.一定私有的上下文结构3.每个线程都有自己独立的栈结构。 2.线程共享
进程的多个线程共享同一地址空间因此Text Segment代码段、Data Segment数据段都是共享的如果定义一个函数在各线程中都可以调用如果定义一个全局变量在各线程中都可以访问到除此之外各线程还共享以下进程资源和环境
文件描述符表每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)当前工作目录用户id和组id 进程和线程的关系如下图 ☀️四、Linux线程控制
1.POSIX线程库
与线程有关的函数构成了一个完整的系列绝大多数函数的名字都是以“pthread_”打头的。要使用这些函数库要通过引入头文件pthread.h。链接这些线程函数库时要使用编译器命令的“-lpthread”选项。
mythread:mythread.ccg -o $ $^ -lpthread -stdc11 //-lpthread - 使用pthread库
.PHONY:clean
clean:rm -f mythread2.创建线程
功能创建一个新的线程
原型int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数thread:返回线程IDattr:设置线程的属性attr为NULL表示使用默认属性start_routine:是个函数地址线程启动后要执行的函数arg:传给线程启动函数的参数
返回值成功返回0失败返回错误码错误检查 传统的一些函数是成功返回0失败返回-1并且对全局变量errno赋值以指示错误。pthreads函数出错时不会设置全局变量errno而大部分其他POSIX函数会这样做而是将错误代码通过返回值返回。pthreads同样也提供了线程内的errno变量以支持其它使用errno的代码。对于pthreads函数的错误建议通过返回值判定因为读取返回值要比读取线程内的errno变量的开销更小
#include unistd.h
#include stdlib.h
#include stdio.h
#include string.h
#include pthread.hvoid* rout(void* arg) {int i;for (; ; ) {printf(Iam thread 1\n);sleep(1);}
}
int main(void)
{pthread_t tid;int ret;if ((ret pthread_create(tid, NULL, rout, NULL)) ! 0) {fprintf(stderr, pthread_create : %s\n, strerror(ret)); //打印创建失败信息exit(EXIT_FAILURE);}int i;for (; ; ) {printf(Iam main thread\n);sleep(1);}
}3.线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法
从线程函数return。return nullptr;这种方法对主线程不适用从main函数return相当于调用exit。线程可以调用pthread_ exit终止自己。一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。 pthread_exit函数
功能线程终止
原型void pthread_exit(void* value_ptr);
参数value_ptr : value_ptr不要指向一个局部变量。
返回值无返回值跟进程一样线程结束的时候无法返回到它的调用者自身需要注意pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的不能在线程函数的栈上分配因为当其它线程得到这个返回指针时线程函数已经退出了。 常见使用方式pthread_exit(nullptr);pthread_cancel函数
功能取消一个执行中的线程
原型int pthread_cancel(pthread_t thread);
参数thread:线程ID
返回值成功返回0失败返回错误码4.线程等待 线程也是需要被等待的如果不等待就会出现类似僵尸进程的问题 - 内存泄漏。 即已经退出的线程其空间没有被释放仍然在进程的地址空间内创建新的线程不会复用刚才退出线程的地址空间。、 线程等待可以获取线程的退出信息并回收对应的PCB等内核资源防止内存泄漏。
功能等待线程结束
原型int pthread_join(pthread_t thread, void **value_ptr);
参数thread:线程IDvalue_ptr:它指向一个指针后者指向线程的返回值void*是一个输出型参数指向线程的返回值
返回值成功返回0失败返回错误码常见用法void *ret nullptr;int n pthread_join(tid, (void**)ret);assert(n 0);当我们线程 return (void*)n 的时候pthread库会为我们维护一个小的变量用于保存这个 (void*)n 变量我们可以通过 (void**)ret 的方法将库中的变量拿出来。我们可以通过下图加深理解int **pp p*ppp*pa若a为我们要取出来的值则void**p两次解引用即可得到a的值。 5.线程控制代码示例
⚡1简单版重点
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include pthread.hvoid* thread1(void* arg)
{printf(thread 1 returning ... \n);int* p (int*)malloc(sizeof(int));*p 1;return (void*)p;
}void* thread2(void* arg)
{printf(thread 2 exiting ...\n);int* p (int*)malloc(sizeof(int));*p 2;pthread_exit((void*)p);
}void* thread3(void* arg)
{while (1) { //printf(thread 3 is running ...\n);sleep(1);}return NULL;
}int main(void)
{pthread_t tid;void* ret;// thread 1 returnpthread_create(tid, NULL, thread1, NULL);pthread_join(tid, ret);printf(thread return, thread id %X, return code:%d\n, tid, *(int*)ret);free(ret);// thread 2 exitpthread_create(tid, NULL, thread2, NULL);pthread_join(tid, ret);printf(thread return, thread id %X, return code:%d\n, tid, *(int*)ret);free(ret);// thread 3 cancel by otherpthread_create(tid, NULL, thread3, NULL);sleep(3);pthread_cancel(tid);pthread_join(tid, ret);if (ret PTHREAD_CANCELED)printf(thread return, thread id %X, return code:PTHREAD_CANCELED\n, tid);elseprintf(thread return, thread id %X, return code:NULL\n, tid);
}运行结果
[rootlocalhost linux]# . / a.out
thread 1 returning ...
thread return, thread id 5AA79700, return code:1
thread 2 exiting ...
thread return, thread id 5AA79700, return code : 2
thread 3 is running ...
thread 3 is running ...
thread 3 is running ...
thread return, thread id 5AA79700, return code : PTHREAD_CANCELED⚡2复杂版 复杂版涉及线程循环创建、等待、删除 makefile
mythread:mythread.ccg -o $ $^ -lpthread -stdc11 //-lpthread - 使用pthread库
.PHONY:clean
clean:rm -f mythreadmythread.cc #include iostream#include cstdlib#include string#include cassert#include vector#include pthread.h#include unistd.husing namespace std;// 当成结构体使用class ThreadData{public:int number;pthread_t tid;char namebuffer[64];};class ThreadReturn{public:int exit_code;int exit_result;};//1. start_routine, 现在是被几个线程执行呢10 这个函数现在是什么状态重入状态//2. 该函数是可重入函数吗是的可在任意时刻被打断恢复运行时不会丢失数据//3. 在函数内定义的变量都叫做局部变量具有临时性 -- 今天依旧适用// -- 在多线程情况下 也没有问题 -- 其实每一个线程都有自己独立的栈结构void *start_routine(void *args){ThreadData *td static_castThreadData *(args); // 安全的进行强制类型转化int cnt 10;while (cnt){cout cnt: cnt cnt: cnt endl; // bugcnt--;sleep(1);// return nullptr; // 线程函数结束return的时候线程就算终止了// pthread_exit(nullptr);// exit(0); // 能不能用来终止线程不能因为exit是终止进程的任何一个执行流调用exit都会让整个进程退出}// 线程如何终止的问题// return (void*)td-number; // warning void *ret (void*)td-number;// return (void *)106;// pthread_exit((void*)111); ThreadReturn * tr new ThreadReturn();tr-exit_code 1;tr-exit_result 106;// ThreadReturn tr; // warning在栈上开辟的空间 return tr退出后数据就拿不到了;//return (void*)tr; //右值return (void*)100;}int main(){// 1. 我们想创建一批线程vectorThreadData* threads;#define NUM 10for(int i 0; i NUM; i){ThreadData *td new ThreadData();td-number i1;snprintf(td-namebuffer, sizeof(td-namebuffer), %s:%d, thread, i1);//将 thread, i1 放入namebufferpthread_create(td-tid, nullptr, start_routine, td);threads.push_back(td);}for(auto iter : threads){cout create thread: iter-namebuffer : iter-tid suceesss endl;}// 线程是可以被cancel取消的注意线程要被取消前提是这个线程已经跑起来了// 线程如果是被取消的退出码-1 (PTHREAD_CANCELED)sleep(5);for(int i 0; i threads.size()/2; i){pthread_cancel(threads[i]-tid); //主线程取消新线程cout pthread_cancel : threads[i]-namebuffer success endl;}for(auto iter : threads) //进程等待{void *ret nullptr; // 注意: 是void *哦// ? : 为什么没有见到线程退出的时候对应的退出信号 线程出异常收到信号整个进程都会退出// pthread_join:默认就认为函数会调用成功不考虑异常问题异常问题是你进程该考虑的问题int n pthread_join(iter-tid, (void**)ret); // void ** retp; *retp return (void*)td-numberassert(n 0);cout join : iter-namebuffer success, exit_code: (long long)ret endl;delete iter;}cout main thread quit endl;return 0;}运行结果 下面是一张另一张结果图哦 ☀️五、C多线程引入 上面章节讲述的都是以C语言为基础的原生线程库应用语言设计者们将其加以封装就形成了C11的多线程。 代码示例
#include iostream
#include unistd.h
#include threadvoid thread_run()
{while (true){std::cout 我是新线程... std::endl;sleep(1);}
}int main()
{// 任何语言在linux中如果要实现多线程必定要是用pthread库// 如何看待C11中的多线程呢C11 的多线程在Linux环境中本质是对pthread库的封装std::thread t1(thread_run);while (true){std::cout 我是主线程... std::endl;sleep(1);}t1.join();return 0;
}运算结果 ☀️结语 多线程1 的知识大概就讲到这里啦博主后续会继续更新更多C 和 Linux的相关知识干货满满如果觉得博主写的还不错的话希望各位小伙伴不要吝啬手中的三连哦你们的支持是博主坚持创作的动力