二、线程操作

发布时间:2026/7/14 19:55:44
二、线程操作 注意使用pthread系列函数编译时必须手动链接线程库gcc test.c-otest-pthread不加-pthread会出现编译 / 运行异常。为什么必须加-pthread-pthread不只是单纯链接-lpthread它同时做两件事链接阶段链接 libpthread 线程库提供 pthread_create / pthread_self / pthread_exit 等函数的实现编译预处理阶段定义宏、调整线程安全的标准库行为如 errno 线程局部存储、锁实现。线程操作(1)pthread_create函数1. 函数介绍1. 函数原型#includepthread.hintpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);2. 返回值成功返回 0失败返回对应错误编号 (不是之前说的 errno 获取错误号信息strerror)*thread 数据无效。3. 参数pthread_t *thread输出参数创建成功后新线程 ID 会存入该指针指向内存创建失败时*thread 内容未定义。const pthread_attr_t *attr线程属性结构体指针非 NULL使用结构体中自定义属性创建线程结构体需先用pthread_attr_init()初始化NULL采用系统默认线程属性。void *(*start_routine)(void *)线程入口函数线程启动后执行该函数返回值为 void*。void *arg传递给入口函数start_routine的唯一参数。4. 作用在调用进程中创建并启动一条新线程。2. 样例 (创建一个子线程)准备一个 pthread_create.c 文件#includestdio.h#includestring.h#includepthread.h#includesys/types.h#includeunistd.hvoid*handle(void*arg){printf(子线程已创建...\n);printf(参数arg: %d\n,*(int*)arg);returnNULL;}intmain(){// 创建新线程pthread_tthread;intnum10;inttidpthread_create(thread,NULL,handle,(void*)num);if(tid!0){char*strstrerror(tid);printf(%s\n,str);}for(inti0;i3;i){printf(pid: %d, i: %d\n,getpid(),i);}sleep(1);return0;}注意上面的代码部分只有handle函数部分是子线程独立运行的代码段。(2)pthread_self函数1. 函数介绍1. 函数原型#includepthread.hpthread_tpthread_self(void);2. 参数无参数。3. 返回值该函数调用永远成功返回调用者线程的 ID类型为 pthread_t。4. 作用获取当前调用进程的 ID 。(3)pthread_exit函数1. 函数介绍1. 函数原型#includepthread.hvoidpthread_exit(void*retval);2. 参数retval线程退出返回值指针作用存放线程结束后要传递给其他线程的返回数据接收方式其他线程调用pthread_join(tid, res)res会接收该指针。取值规则若不需要返回数据传 NULL若要传递数据不能传入栈局部变量地址线程销毁后栈会释放内存失效建议传全局变量 / 堆内存地址3. 返回值函数无返回值调用后当前线程立刻终止代码不会回到调用 pthread_exit 的下一行继续执行。4. 作用终止一个线程在哪个线程中调用就表示终止哪个线程。2. 样例创建一个 pthread_exit.c 文件#includestdio.h#includestring.h#includepthread.h#includesys/types.h#includeunistd.hvoid*rollback(void*arg){printf(child thread id : %ld\n,pthread_self());returnNULL;}intmain(){// 创建新线程pthread_ttid;intarg10;intretpthread_create(tid,NULL,rollback,(void*)arg);if(ret!0){char*errstrstrerror(ret);printf(error : %s\n,errstr);}// 主线程for(inti1;i3;i){printf(i %d\n,i);}printf(tid: %ld, main thread id: %ld\n,tid,pthread_self());// 终止主线程pthread_exit(NULL);printf(main thread die...\n);return0;}可以看到 “main thread die…” 并没有输出因此可以得到终结线程后的代码将不会执行。(4)pthread_join函数1. 函数介绍1. 函数原型#includepthread.hintpthread_join(pthread_tthread,void**retval);2. 参数pthread_t thread待等待的目标线程 ID限制该线程必须是 joinable默认新建线程都是 joinable调用 pthread_detach 后不可 join。void **retval输出型二级指针用于接收目标线程的退出返回值传 NULL代表不需要接收线程退出结果非空会写入pthread_exit()传入的指针线程被取消则写入PTHREAD_CANCELED。3. 返回值成功返回 0失败返回对应错误编号。4. 作用该函数会阻塞等待参数thread指定的线程终止。2. 样例创建一个 pthread_join.c 文件#includestdio.h#includestring.h#includepthread.h#includesys/types.h#includeunistd.hintnum10;//这里一定要用全局变量(存在数据段)或者堆内存线程结束栈内存会被回收void*rollback(void*arg){printf(child thread id : %ld\n,pthread_self());sleep(3);// 测试pthread_join是否阻塞pthread_exit((void*)num);// 等同于(void *)num}intmain(){// 创建新线程pthread_ttid;intarg10;intretpthread_create(tid,NULL,rollback,(void*)arg);if(ret!0){char*errstrstrerror(ret);printf(error : %s\n,errstr);}// 主线程for(inti1;i3;i){printf(i %d\n,i);}printf(tid: %ld, main thread id: %ld\n,tid,pthread_self());// 阻塞等待回收子线程void*thread_val;pthread_join(tid,thread_val);printf(main thread rece: %d\n,*(int*)thread_val);// 终止主线程pthread_exit(NULL);printf(main thread die...\n);return0;}(5)pthread_detach函数1. 函数介绍1. 函数原型#includepthread.hintpthread_detach(pthread_tthread);2. 参数pthread_t thread目标线程 ID指定要设置为分离态的线程限制该线程当前必须是可汇合 (joinable) 状态不能已经被分离。3. 返回值成功返回 0失败返回对应错误编号。4. 作用修改线程属性为分离态改变资源回收规则默认新建线程是 joinable退出后资源残留必须pthread_join回收detach 之后线程一旦结束操作系统自动释放线程栈、线程管理结构体不会内存泄漏。解除 “必须等待” 的限制主线程无需阻塞等待该线程执行完毕二者完全异步运行。2. 样例创建一个 pthread_detach.c 文件#includestdio.h#includestring.h#includepthread.h#includesys/types.h#includeunistd.hintnum10;//这里一定要用全局变量(存在数据段)或者堆内存线程结束栈内存会被回收void*rollback(void*arg){printf(child thread id : %ld\n,pthread_self());sleep(3);// 测试pthread_detach是否阻塞return(void*)num;}intmain(){// 创建新线程pthread_ttid;intarg10;intretpthread_create(tid,NULL,rollback,(void*)arg);if(ret!0){char*errstrstrerror(ret);printf(error : %s\n,errstr);}// 主线程for(inti1;i3;i){printf(i %d\n,i);}printf(tid: %ld, main thread id: %ld\n,tid,pthread_self());// 设置子线程分离,子线程分离后子线程结束时对应的资源就不需要主线程释放retpthread_detach(tid);if(ret!0){char*errstrstrerror(ret);printf(error1 : %s\n,errstr);}// 设置分离后对分离的子线程进行连接 pthread_join()void*thread_val;retpthread_join(tid,thread_val);if(ret!0){char*errstrstrerror(ret);printf(error2 : %s\n,errstr);}printf(main thread rece: %d\n,*(int*)thread_val);// 终止主线程pthread_exit(NULL);printf(main thread die...\n);return0;}可以看到将线程分离后再次进行连接将会错误。(6)pthread_cancel函数1. 函数介绍1. 函数原型#includepthread.hintpthread_cancel(pthread_tthread);2. 参数pthread_t thread目标线程 ID代表要发送取消信号的线程支持对自身线程、其他线程发送取消请求。3. 返回值成功返回 0失败返回对应错误编号。4. 作用pthread_cancel()函数向thread指定的目标线程发送取消请求。目标线程是否响应、何时响应这个取消请求由该线程自身的两个属性控制state (取消使能状态) 和 type (取消类型)。5. 两大关键控制属性目标线程自身控制是否响应取消① 取消使能状态pthread_setcancelstate函数设置 statePTHREAD_CANCEL_ENABLE默认接收并处理取消请求PTHREAD_CANCEL_DISABLE忽略取消请求请求排队暂存直到重新开启。② 取消类型pthread_setcanceltype函数设置 typePTHREAD_CANCEL_DEFERRED默认延迟取消不会立刻终止仅在线程调用取消点函数read/write/pthread_join/sleep等系统 IO、同步函数时才触发取消流程业务计算循环、纯 CPU 运算代码段不会被中途打断安全。PTHREAD_CANCEL_ASYNCHRONOUS异步取消线程任意代码位置都可能被随时终止风险极高极易造成资源泄漏、死锁极少使用。6. 线程被取消后的固定执行流程逆序执行所有清理函数pthread_cleanup_push执行线程私有数据 TSD 析构函数线程正式终止退出。2. 样例创建一个pthread_cancel.c 文件#includestdio.h#includestring.h#includepthread.h#includesys/types.h#includeunistd.hintnum10;//这里一定要用全局变量(存在数据段)或者堆内存线程结束栈内存会被回收void*rollback(void*arg){printf(child thread id : %ld\n,pthread_self());sleep(1);for(inti1;i100;i){printf(child pthread i: %d\n,i);}return(void*)num;}intmain(){// 创建新线程pthread_ttid;intarg10;intretpthread_create(tid,NULL,rollback,(void*)arg);if(ret!0){char*errstrstrerror(ret);printf(error : %s\n,errstr);}// 设置子线程分离,子线程分离后子线程结束时对应的资源就不需要主线程释放pthread_detach(tid);// 终止指定子进程pthread_cancel(tid);for(inti1;i3;i){printf(i %d\n,i);}printf(tid: %ld, main thread id: %ld\n,tid,pthread_self());// 终止主线程pthread_exit(NULL);printf(main thread die...\n);return0;}可以看到子线程并没有输出循环体的内容就被父进程给终止了。这是因为sleep函数本身是取消点函数当取消请求队列中有取消信号则会终止该线程。线程属性线程属性类型pthread_attr_t。(1) 初始化、释放线程属性资源#includepthread.hintpthread_attr_init(pthread_attr_t*attr);intpthread_attr_destroy(pthread_attr_t*attr);描述pthread_attr_init()函数会使用默认值初始化attr指向的线程属性对象。调用该函数后可以通过配套系列函数单独修改属性对象里的各项配置之后该属性对象可以重复用于一次或多次pthread_create()创建线程。对一个已经初始化完成的属性对象再次调用pthread_attr_init()程序行为属于未定义。当线程属性对象不再使用时应当调用pthread_attr_destroy()销毁释放内部资源。销毁属性对象不会对以往依靠该属性创建出的线程产生任何影响。属性对象被销毁后可以再次调用pthread_attr_init()重新初始化复用对已经销毁、未重新初始化的属性对象做任何其他操作结果都是未定义行为。返回值两个函数调用成功均返回 0执行失败时返回非 0 错误码。(2) 设置、获取线程属性#includepthread.hintpthread_attr_getdetachstate(constpthread_attr_t*attr,int*detachstate);intpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate);描述pthread_attr_setdetachstate()函数用于设置线程属性对象attr的分离状态属性参数detachstate指定目标状态。该属性决定了使用此属性对象创建出的线程默认是可汇合joinable还是分离detached 状态。detachstate仅支持以下两个宏取值PTHREAD_CREATE_DETACHED使用该属性创建的线程直接为分离态。PTHREAD_CREATE_JOINABLE使用该属性创建的线程为可汇合态。刚通过pthread_attr_init()初始化完成的线程属性对象分离状态默认值PTHREAD_CREATE_JOINABLE。pthread_attr_getdetachstate()函数会将属性对象attr中存储的分离状态存入detachstate指针指向的内存中返回。返回值两个函数调用成功均返回 0执行失败时返回非 0 的错误码。(3) 样例创建一个 pthread_attr.c 文件#includestdio.h#includestring.h#includepthread.h#includesys/types.h#includeunistd.hintnum10;//这里一定要用全局变量(存在数据段)或者堆内存线程结束栈内存会被回收void*rollback(void*arg){printf(child thread id : %ld\n,pthread_self());sleep(1);return(void*)num;}intmain(){// 设置线程属性pthread_attr_tattr;pthread_attr_init(attr);// 初始化intretpthread_attr_setdetachstate(attr,PTHREAD_CREATE_DETACHED);// 设置由该属性创建的线程为分离态if(ret!0){char*errstrstrerror(ret);printf(error1 : %s\n,errstr);}// 查看新建的属性intdetachstate;pthread_attr_getdetachstate(attr,detachstate);if(detachstatePTHREAD_CREATE_DETACHED){printf(新建属性: detached state\n);}elseif(detachstatePTHREAD_CREATE_JOINABLE){printf(新建属性: joinable state\n);}// 创建新线程pthread_ttid;intarg10;retpthread_create(tid,attr,rollback,(void*)arg);// 创建子线程的同时设置属性if(ret!0){char*errstrstrerror(ret);printf(error2 : %s\n,errstr);}printf(tid: %ld, main thread id: %ld\n,tid,pthread_self());pthread_attr_destroy(attr);// 释放线程属性资源// 终止主线程pthread_exit(NULL);printf(main thread die...\n);return0;}