ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

线程概念与多线程相关函数——创建与终止、线程标识、线程回收、线程控制函数

线程概念与多线程相关函数——创建与终止、线程标识、线程回收、线程控制函数 一、线程概念在Linux内核中没有独立的“线程”数据结构线程的本质是“共享资源的轻量级进程(LightweightProcess简称LWP)”线程的状态在Linux系统中线程作为内核调度的基本单元(轻量级进程)会因资源占用、执行阶段不同呈现多种状态运行状态(R)也称之为可运行状态就是处于运行状态的线程正在CPU执行或等待CPU调度可中断睡眠状态(S)线程在等待某个事件(如等待I/O操作、等待特定信号等)完成可被信号唤醒不可以中断睡眠状态(D)线程正在等待关键硬件操作完成不能被信号中断确保数据完整性停止状态(T)线程被暂停执行通常是收到了特定的信号(如SIGSTOP(暂停线程))或者在调试程序时被调试器暂停可被信号恢复执行(如SIGCONT)僵尸状态(Z)线程已终止相关资源未被回收等待所属进程(或其他负责线程资源回收的机制)进行回收死亡态(X)线程彻底终止资源全部释放是一个瞬间状态几乎无法观测到线程编译编码时需包含头文件#include pthread.h声明库中函数接口编译时需显式链接线程库gcc 源码.c -o 可执行文件-lpthread否则会报 “未定义引用” 错误添加-lpthread 选项的原因使用的线程库在大多数系统中不是默认链接的标准库而是作为一个独立的共享库存在编译器无法找到该线程库-lpthread选项是告诉编译器去查找链接pthread线程库确保代码中使用线程相关函数可以找到实际代码。在vscode中配置-lpthread在左侧菜单栏扩展中打开 SSH 已安装软件找到 Code Runner打开 Code Runner 设置找到 Code-runner: Executor Map点击 “在settings.json中编辑” 打开 settings.json 文件找到C语言编译命令在 “gcc $fileName -o $fileNameWithoutExt” 后面加上 “-lpthread”如果运行程序出现下面情况则需要修改tasks.json里的 gcc 命令在后面添加-pthread参数再次运行进程与线程的核心区别注意主线程与子线程执行没有先后顺序CPU进行时间片轮转谁先抢到时间片谁先执行一个进程中的多线程共享进程的内存空间所以主线程与子线程访问全局变量打印的地址相同但访问局部变量打印出的地址不同针对局部变量每个线程都有自己的栈区//测试主线程与子线程的执行顺序 /* 主线程与子线程执行没有先后顺序CPU进行时间片轮转谁先抢到时间片谁先执行 */ //测试主线程与子线程是否共享同一块内存空间 /* 一个进程中的多线程共享进程的内存空间所以主线程与子线程访问全局变量打印的地址相同 但访问局部变量打印出的地址不同针对局部变量每个线程都有自己的栈区 */ #include stdio.h #include pthread.h #include string.h #include unistd.h #include stdlib.h int num100; //线程入口函数 //void*返回值可以返回任意类型的指针 void* pthread_start(void *arg){ while(1){ printf(子进程,全局变量num%d,num%p\n,num,num); sleep(1); } return NULL;//此处返回是没有实际意义但是不能丢 } int main(int argc,const char *argv[]){ pthread_t tid 0; int ret0; if(0 ! (ret pthread_create(tid,NULL,pthread_start,NULL))){ fprintf(stderr,线程创建失败,错误码%d,错误信息%s\n,ret,strerror(ret)); } printf(线程创建成功线程id%ld\n,tid); while(1){ printf(主进程,全局变量num%d,num%p\n,num,num); sleep(1); } return 0; }二、多线程相关函数一线程创建与终止函数pthread_create函数所需头文件#include pthread.h原型int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);在调用进程中创建一个新的线程子线程参数thread用于存储新创建线程的唯一标识符(线程ID)attr用于指定新线程的属性若是NULL则使用系统默认属性start_routine指向线程的入口函数函数指针void *(*start_routine) (void *)arg用于给线程入口函数传递参数若无需参数可设为NULL返回值成功则返回0新线程ID会写入thread指向的变量中失败则返回非0错误码不能使用perror打印错误信息perror使用的是系统中存在的错误码因为线程库函数返回的是POSIX错误码所以可以使用strerror打印错误信息使用进行格式化输出错误信息如 fprintf 。#include stdio.h #include pthread.h #include string.h #include unistd.h #include stdlib.h //线程入口函数 //void*返回值可以返回任意类型的指针 void* pthread_start(void *arg){ printf(线程入口函数\n); #if 0 //接收传递参数--栈区 int *val(int *)arg; printf(接收传递过来的参数%d\n,*val); #else //接收传递参数--堆区 int *val(int *)arg; printf(接收传递过来的参数%d\n,*val); //释放堆区空间 free(val); #endif return NULL;//此处返回是没有实际意义但是不能丢 } int main(int argc,const char *argv[]){ pthread_t thread_id 0; int ret0; #if 0 //栈区给入口函数传递参数 /* 如果主线程在子线程之前结束那么value变量就会被销毁 所以当子线程访问此变量就会出现访问的是一个无效地址 针对栈区给入口函数传递参数是不安全的此时考虑使用堆区进行传递参数 */ int val100; if(0 ! (ret pthread_create(thread_id,NULL,pthread_start,(void *)(val)))){ fprintf(stderr,线程创建失败,错误码%d,错误信息%s\n,ret,strerror(ret)); } printf(线程创建成功thread_id%ld\n,thread_id); #else //使用堆区给入口函数传递参数 /* 需要手动管理如果忘记释放可能造成内存泄漏 */ int *val(int *)malloc(sizeof(int)); if(val NULL){ printf(堆区开辟空间失败\n); return -1; }printf(堆区开辟空间成功\n); *val123; if(0 ! (ret pthread_create(thread_id,NULL,pthread_start,(void *)val))){ fprintf(stderr,线程创建失败,错误码%d,错误信息%s\n,ret,strerror(ret)); } printf(线程创建成功thread_id%ld\n,thread_id); #endif sleep(3); return 0; }pthread_exit函数所需头文件#include pthread.h原型void pthread_exit(void *retval);结束当前线程不影响其他线程运行无返回值参数retval用来给回收资源的线程传递线程的退出状态值若不想返回退出状态值可以传NULLpthread_exit(NULL); //子线程已终止在此之后的代码不再执行二线程标识函数pthread_self函数所需头文件#include pthread.h原型pthread_t pthread_self(void);获取调用线程的线程ID用户空间线程库分配的id可以通过命令ps -L查看线程 id (LWP 字段)内核给该线程分配的id与pthread_self获取的线程 id 不一样返回值总是成功返回调用线程的线程IDprintf(主进程主线程id%ld\n,pthread_self());三线程回收函数pthread_join函数所需头文件#include pthread.h原型int pthread_join(pthread_t thread,void **retval);阻塞等待指定的结合态的线程结束为其回收资源。例如 pthread_join(tid,NULL);结合态线程线程结束没有为其回收资源的线程需要由其他线程调用 pthread_join() 函数回收结合态线程的资源参数thread存储要回收资源的线程IDretval用来接收线程退出的状态值不关心传NULL即可返回值成功则返回0失败则返回非0错误码。资源回收管理模式线程创建后默认处于结合态若未显示设置为分离态其终止后资源不会立即释放必须由其他线程(通常是主线程)通过pthread_join( )主动回收否则会造成资源泄露问题。pthread_attr_init函数所需头文件#include pthread.h原型int pthread_attr_init(pthread_attr_t *attr);将一个线程属性对象初始化为默认值参数attr需要被初始化的线程属性对象返回值成功则返回0失败则返回非0错误码pthread_attr_setdetachstate函数所需头文件#include pthread.h原型int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);设置线程属性对象的分离状态参数attr设置分离状态的线程属性对象detachstate用于指定线程的分离状态PTHREAD_CREATE_JOINABLE表示将线程设置为结合态PTHREAD_CREATE_DETACHED表示将线程设置为分离态返回值成功则返回0失败则返回非0错误码。pthread_attr_destroy函数所需头文件#include pthread.h原型int pthread_attr_destroy(pthread_attr_t *attr);销毁一个已初始化的线程属性对象参数attr指定需要被销毁的线程属性对象返回值成功则返回0失败则返回非0错误码。四线程控制函数pthread_detach函数所需头文件#include pthread.h原型int pthread_detach(pthread_t thread);标记一个线程为分离态系统会自动回收器资源参数thread线程id返回值成功则返回0失败则返回非0错误码。pthread_cancel函数所需头文件#include pthread.h原型int pthread_cancel(pthread_t thread);请求取消指定的线程(调用成功只能说明请求取消操作完成不能说明线程会立刻终止)目标线程对取消请求的处理取决于取消状态和取消类型(函数)参数thread要取消的线程ID返回值成功则返回0失败则返回非0错误码。pthread_setcancelstate函数取消状态所需头文件#include pthread.h原型int pthread_setcancelstate(int state,int *oldstate); 设置调用线程的可取消状态PTHREAD_CANCEL_ENABLE可被取消(默认)PTHREAD_CANCEL_DISABLE不可被取消参数state新的可取消状态oldstate旧的可取消状态返回值成功则返回0失败则返回非0错误码。pthread_setcanceltype函数取消类型所需头文件#include pthread.h原型int pthread_setcanceltype(int type,int *oldtype); 设置调用线程的可取消类型PTHREAD_CANCEL_DEFERRED延时取消(默认)直到下一次调用能作为取消点的函数时才被取消PTHREAD_CANCEL_ASYNCHRONOUS可被立即取消参数type要设置的新的可取消类型值oldtype存储旧的可取消类型值返回值成功则返回0失败则返回非0错误码。//请求取消指定线程 /* 请求取消指定线程取决于现成的取消状态和取消类型 取消状态可取消状态(默认)与不可取消状态 取消类型延时取消(默认)标记打上继续运行直到遇到取消点。 和可被立即取消(异步取消)只要再次被调度随时被杀不需要等待任何取消点。 */ #include stdio.h #include pthread.h #include string.h #include unistd.h #include stdlib.h //void*返回值可以返回任意类型的指针 void* thread1(void *arg){//线程入口函数 //设置线程1的取消状态与取消类型 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL); while(1){ //结果不会每隔一秒打印因为线程1收到请求取消后线程1会在取消点进行取消 /* 1. 线程 1printf(线程1正在运行\n); → 打印输出然后进入sleep(1)进入内核休眠 2. 就在线程 1sleep 休眠的这段时间主线程执行pthread_cancel(tid1)给线程 1 打上【待取消标记】 3. 等待 sleep 的 1 秒计时结束线程准备从 sleep 内核调用返回用户态。 sleep 本身就是取消点在 sleep 系统调用返回这一刻检测到取消标记直接终止线程不会回到循环、不会再次执行 printf */ printf(线程1正在运行\n); sleep(1); } return NULL; } void* thread2(void *arg){//线程入口函数 //设置线程2的取消状态与取消类型 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); while(1){ //因为线程2收到请求取消后会立即取消不再打印 /* 1. 线程 2 高速循环不停执行printf疯狂输出线程2正在运行。 2. 主线程执行pthread_cancel(tid2)内核给线程 2 打上待取消标记pthread_cancel返回成功打印请求取消线程2成功。 3. 接下来看 CPU 调度主线程发送完 cancel 请求后CPU 切回线程 2。 线程 2 刚被调度上 CPU在执行下一条机器指令前内核检测到取消标记直接终止线程。→ 来不及进入 printf 执行打印。 请求取消线程2成功这行输出之后几乎再也看不到线程 2 打印。 */ printf(线程2正在运行\n); // sleep(1); //注释掉不会再打印 } return NULL; } void* thread3(void *arg){//线程入口函数 //设置线程3的取消状态与取消类型---直接忽略取消标记 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL); while(1){ printf(线程3正在运行\n); sleep(1); } return NULL; } int main(int argc,const char *argv[]){ //1.可取消状态延时取消 pthread_t tid1 0; int ret10; if(0 ! (ret1 pthread_create(tid1,NULL,thread1,NULL))){ fprintf(stderr,线程1创建失败,错误信息%s\n,strerror(ret1)); return -1; } printf(线程1创建成功线程1id%ld\n,tid1); //2.可取消状态可被立即取消 pthread_t tid2 0; int ret20; if(0 ! (ret2 pthread_create(tid2,NULL,thread2,NULL))){ fprintf(stderr,线程2创建失败,错误信息%s\n,strerror(ret2)); return -1; } printf(线程2创建成功线程2id%ld\n,tid2); //3.不可取消状态 pthread_t tid3 0; int ret30; if(0 ! (ret3 pthread_create(tid3,NULL,thread3,NULL))){ fprintf(stderr,线程3创建失败,错误信息%s\n,strerror(ret3)); return -1; } printf(线程3创建成功线程3id%ld\n,tid3); //请求取消指定线程1--调用成功只能说明取消成功不能说明立即取消 int ret_cancel 0; if(0 ! (ret_cancel pthread_cancel(tid1))){ fprintf(stderr,请求取消线程1失败%s\n,strerror(ret_cancel)); return -1; } printf(请求取消线程1成功\n); //请求取消线程2 int ret_cancel2 0; if(0 ! (ret_cancel2 pthread_cancel(tid2))){ fprintf(stderr,请求取消线程2失败%s\n,strerror(ret_cancel2)); return -1; } printf(请求取消线程2成功\n); //请求取消线程3 int ret_cancel3 0; if(0 ! (ret_cancel3 pthread_cancel(tid3))){ fprintf(stderr,请求取消线程3失败%s\n,strerror(ret_cancel3)); return -1; } printf(请求取消线程3成功\n); //阻塞等待线程退出join结合态线程 pthread_join(tid1,NULL); pthread_join(tid2,NULL); pthread_join(tid3,NULL); return 0; }
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表