南昌智能建站模板,下沙网站优化,网络技术有限公司是什么,wap建站程序哪个好1.线程概念线程是进程内的最小执行单元#xff0c;一个进程可以包含多个线程#xff0c;所有线程共享进程的资源#xff08;内存、文件句柄等#xff09;#xff0c;但有自己独立的执行栈和程序计数器。结合进程的核心区别可以这样理解#xff1a;进程是资源分配的基本单…1.线程概念线程是进程内的最小执行单元一个进程可以包含多个线程所有线程共享进程的资源内存、文件句柄等但有自己独立的执行栈和程序计数器。结合进程的核心区别可以这样理解进程是资源分配的基本单位系统给进程分配内存、CPU 时间片等资源进程像是一个 “独立的工作车间”。线程是 CPU 调度的基本单位线程是车间里的 “工人”一个车间可以有多个工人他们共享车间的工具和材料进程资源但各自干不同的活且切换工人的开销远小于切换车间。要是想使用线程就得自己重新定义线程的结构体相当于从头重新弄一套线程相关的实现而 Linux 那边好像是直接用了原本就有的线程那一套机制把它叫做轻量级进程wiindows 系统就算重新弄了一套相关的实现我们可以简单理解成线程:是进程内的一个执行分支如何理解我们以前的进程???操作系统以进程为单位给我们分配资源我们当前的进程内部只有一个线程(主线程)!Linux 实现方案在 Linux 中线程在进程 “内部” 执行线程在进程的地址空间内运行任何执行流要执行都要有资源地址空间是进程的资源窗口2.线程的性质同一进程内的多个线程共享内核空间的数据页表且共享用户空间页表因线程共享进程的页表根目录 PGD内核空间映射全局统一不同进程的线程逻辑上共享内核空间页表所有进程的内核空间页表映射完全一致系统启动时初始化一次全局复用但用户空间页表相互独立。我们还是用以前虚拟地址的图片相同进程不同线程不同进程不同线程内核空间页表相同相同用户空间页表相同不同假设我们创建了n个进程内核空间页表只有1份用户空间页表有n份无论一个进程里面有几个线程 一个进程里面只有一份独立的用户空间页表和一份和其他进程共享的一份内核空间页表线程切换比进程快 为什么线程共享进程的地址空间、文件等资源切换时不用换页表、刷新缓存这些是进程切换的大开销仅需保存 / 恢复 CPU 执行状态比如寄存器、程序计数器不用处理进程级的资源上下文。单个线程如果出现除零野指针问题导致线程崩溃进程也会随着崩溃线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程进程终止该 进程内的所有线程也就随即退出3.虚拟地址到物理地址的转换方式高 10 位PDE 索引 楼栋号小区太大先按 “楼栋” 分组页目录就是小区的「楼栋列表」CR3 寄存器是小区大门的指引牌告诉你楼栋列表放在小区哪个角落。高 10 位就是你快递上的 “楼栋号”用它查楼栋列表就能找到目标快递所在的楼栋对应 “找到目标页表的物理地址”。中 10 位PTE 索引 楼层号找到楼栋后还要找具体楼层页表就是这栋楼的「楼层列表」。中 10 位是 “楼层号”用它查楼层列表就能找到目标快递所在的那一层对应 “找到目标物理页帧的地址”。低 12 位页内偏移 房间号每一层的户型大小都一样都是 4KB因为 2^124096 字节房间号范围固定0-4095。低 12 位直接就是 “房间号”不用再查列表直接顺着楼层找到对应的房间对应 “物理页帧内的具体字节位置”。如果不拆分直接用 32 位虚拟地址对应物理地址会有两个大问题浪费内存32 位系统最大虚拟地址是 4GB按 4KB 一页算需要 100 万个 “页表项”4GB/4KB1024*1024每个页表项占 4 字节一个进程的页表就占 4MB。如果有 1000 个进程光页表就占 4GB内存直接炸了拆分后101012页目录只需要 1024 个项10 位占 4KB1024*4 字节每个页表也只需要 1024 个项占 4KB只有进程用到的页表才会加载到内存不用一次性加载所有页表比如一个进程只用到 10 个页总页表占用才 4KB页目录10*4KB页表44KB比 4MB 省了近 100 倍查找更快拆分后是 “三级索引”页目录→页表→页内偏移每级都是固定长度的索引10 位、10 位、12 位CPU 的 MMU内存管理单元能像 “查字典按部首→页码→行数” 一样硬件级快速计算地址比线性查找快得多。4.线程接口线程函数的接口统一的一个头文件#include pthread.h1.pthread_create作用是启动一个新的执行流线程。int pthread_create( pthread_t *thread, // 输出新线程的ID类似进程PID const pthread_attr_t *attr, // 线程属性NULL表示用默认属性 void *(*start_routine)(void *), // 线程要执行的函数函数指针 void *arg // 传递给start_routine的参数 );thread传入一个pthread_t类型的指针函数会把新线程的 ID 写入这个指针指向的变量。attr线程属性如栈大小、调度策略一般填NULL用默认属性。start_routine线程要执行的函数必须是void* (*)(void*)类型入参是void*返回值是void*。arg传递给start_routine的参数若要传多个参数需封装成结构体指针。返回值成功返回0失败返回错误码不是-1需用strerror()查看错误信息。2. pthread_joinint pthread_join( pthread_t thread, // 要等待的子线程ID由pthread_create生成 void **retval // 输出存储子线程的返回值不需要则填NULL );thread目标子线程的 IDpthread_t类型由pthread_create写入。retval二级指针用于接收子线程return的void*返回值若无需返回值填NULL。返回值成功返回0失败返回错误码非-1可用strerror()查看错误信息。阻塞等待调用pthread_join的线程通常是主线程会阻塞直到目标子线程执行完毕。回收资源子线程结束后若不调用pthread_join其资源如线程控制块 TCB、栈不会自动释放会成为 “僵尸线程”导致资源泄露。获取返回值可以拿到子线程的执行结果子线程通过return返回的void*数据。3.pthread_self()获取当前线程 IDpthread_t pthread_self(void);返回值当前线程的 pthread_t 类型 ID。4.pthread_exit()void pthread_exit(void *retval);前面我们学过的exit和_exit是进程的主动退出方式pthread_exit()是线程的主动退出方式立即终止调用线程将 retval 作为线程的退出状态可被 pthread_join 获取。retval线程退出的返回值指针不能指向线程栈上的局部变量因为线程退出后栈会被销毁。返回值无线程已终止不会返回。线程函数中可显式调用 pthread_exit 退出也可通过 return 返回效果等价return 的返回值会被当作 pthread_exit 的 retval。主线程特殊行为主线程调用 pthread_exit 后主线程终止但进程不会退出其他子线程会继续运行区别于 exit()exit() 会终止整个进程。注意主线程从main函数return时其他线程会被终止但这一现象的本质并非 “主线程 return 直接终止子线程”而是主线程 return 触发了进程退出进程退出会导致所有线程被内核终止。普通子线程的return 该线程结束返回退出状态 继续运行 不受影响正常运行无论是子线程还是主线程调用exit的结果都是一样的 进程退出5.pthread_detach()int pthread_detach(pthread_t thread);设置线程为分离状态将指定线程设置为分离状态detached线程退出后其占用的系统资源会被内核自动回收无需调用 pthread_join 等待。thread需要设置为分离状态的线程 ID。返回值成功返回 0失败返回非 0 的错误码如 EINVAL线程无效或已分离ESRCH线程不存在。主线程中调用pthread_detach(tid)tid 是创建的线程 ID线程内部调用pthread_detach(pthread_self())推荐线程自身控制分离状态线程一旦被分离无法再被 pthread_join 等待调用 pthread_join 会返回 EINVAL 错误。不能对已被 pthread_join 的线程调用 pthread_detach会失败。若线程未被分离且未被 pthread_join退出后会变成僵尸线程占用系统资源直到进程退出。5.pthread_cancel()int pthread_cancel(pthread_t thread);发送线程取消请求向指定线程发送取消请求请求线程终止运行。线程是否响应、何时响应取决于其取消状态和取消类型。参数thread需要取消的线程 ID。返回值成功返回 0失败返回非 0 的错误码如 ESRCH线程不存在。pthread_cancel() 仅发送请求并非立即终止线程。线程响应取消后会返回 PTHREAD_CANCELED宏定义值为 (void*)-1可被 pthread_join 获取。我们可以写三个代码用来测试这些函数#include stdio.h #include pthread.h void *thread_work(void *arg) { printf(【子线程】启动\n); unsigned long tid (unsigned long)pthread_self(); printf(【pthread_self()】执行成功子线程自身ID%lu\n, tid); printf(【pthread_exit()】即将执行执行后子线程终止后续代码不会运行\n); pthread_exit(NULL); printf(【pthread_exit()】未生效此行不该打印\n); } int main() { pthread_t tid; printf(【主线程】pthread_create()即将创建子线程\n); pthread_create(tid, NULL, thread_work, NULL); printf(【pthread_create()】执行成功创建的子线程ID%lu\n, (unsigned long)tid); printf(【主线程】pthread_join()即将等待子线程结束\n); pthread_join(tid, NULL); printf(【pthread_join()】执行成功子线程已结束\n); return 0; }#include stdio.h #include pthread.h #include unistd.h // 线程工作函数极简版 void* thread_func(void* arg) { char* name (char*)arg; for (int i 1; i 3; i) { // 循环3次简化次数 printf(线程[%s]第%d次执行\n, name, i); sleep(1); // 取消点响应cancel } printf(线程[%s]执行完毕\n, name); // 被取消则不执行 return NULL; } int main() { pthread_t t1, t2; // 创建线程t1不取消、t2将取消 pthread_create(t1, NULL, thread_func, t1(不取消)); pthread_create(t2, NULL, thread_func, t2(将取消)); sleep(2); // 让t2执行2次后取消 pthread_cancel(t2); // 发送取消请求 // 等待线程结束 pthread_join(t1, NULL); pthread_join(t2, NULL); return 0; }#include stdio.h #include pthread.h #include unistd.h #include string.h // 线程工作函数极简版 void* thread_func(void* arg) { printf(线程[%s]执行\n, (char*)arg); sleep(1); // 模拟工作 printf(线程[%s]结束\n, (char*)arg); return NULL; } int main() { pthread_t t1, t2; int ret; // 创建非分离线程t1、分离线程t2 pthread_create(t1, NULL, thread_func, t1(未分离)); pthread_create(t2, NULL, thread_func, t2(已分离)); pthread_detach(t2); // 设置分离 // 尝试join非分离线程t1成功 ret pthread_join(t1, NULL); printf(join t1%s\n, ret ? strerror(ret) : 成功); // 尝试join分离线程t2失败 ret pthread_join(t2, NULL); printf(join t2%s预期失败\n, ret ? strerror(ret) : 成功); sleep(1); return 0; }5.修饰符__thread其核心作用是让被修饰的变量成为每个线程的私有副本而非进程级的共享变量。___thread只能对内置类型使用 不能对自定义类型使用1.__thread定义在线程函数内时主线程是否有副本完全取决于主线程是否执行了这个定义了__thread变量的函数—— 只有执行了函数才会触发副本的分配未执行则无副本。2.__thread定义在全局时 主线程必定拥有副本这是进程启动的默认行为与是否使用变量无关所有子线程创建时会自动获得独立副本初始化值为编译期常量#include stdio.h #include pthread.h // 普通全局变量共享 int s 0; // __thread变量线程私有 __thread int t 0; // 线程函数仅做一次自增打印精简核心逻辑 void* f(void* arg) { int id *(int*)arg; s, t; // 共享变量自增私有变量自增 printf(线程%d: s%d, t%d\n, id, s, t); return NULL; } int main() { pthread_t a, b; int id11, id22; pthread_create(a, NULL, f, id1); pthread_create(b, NULL, f, id2); pthread_join(a, NULL); pthread_join(b, NULL); printf(主线程: s%d, t%d\n, s, t); // 主线程的t是独立副本 return 0; }6.线程管理我们学语言的时候知道 临时变量是有生命周期的 是位于栈上的对于线程同样适用主线程和子线程一样有它们的栈区 但是子线程和主线程栈的位置在进程地址空间的不同地方主线程的栈区位于进程地址空间中的用户地址空间中的栈区而线程为了方便统一管理 被封装到一个库里面 运行的时候会被加载到内存的共享区子进程的栈区位于进程地址空间中的用户地址空间中的mmap即共享区暂时先这么理解这张图右侧的struct pthread对应的是用户态的线程控制块TCB主线程的tcb也在其中但是主线程的栈是特殊的位于进程默认栈区—— 主线程的 TCBstruct pthread虽然在 mmap 区但它记录的主线程栈地址是「进程默认栈区」那么看到前面代码的运行结果你不好奇为什么线程的tid这么大呢线程在里面叫轻量化进程 进程有pid 线程同样也有tid每一个线程的库级别的 tcb 的起始地址叫做线程的 tid线程在里面叫轻量化进程 进程有pcb 线程同样也有tcb维度主线程 ID子线程 ID用户态pthread_t类型为pthread_t指向主线程的 TCB 地址类型为pthread_t指向子线程的 TCB 地址内核态LWPpid_t整数类型本质是内核轻量级进程编号等于当前进程的 PID内核全局唯一pid_t整数类型本质是内核轻量级进程编号内核分配的独立pid_t类型整数≠进程 PID内核全局唯一