目录1、概念2、线程API函数2.1创建线程2.2线程属性获取线程属性API设置线程属性API主要的几个属性API获取、设置线程的分离属性以下接口跟线程的调度相关获取、设置线程是否继承创建者的调度策略获取、设置线程的调度策略线程的静态优先级和动态优先级的设置跟线程的栈和警戒区的大小相关API获取、设置线程栈大小、警戒区大小退出、接合线程给指定线程发送一个取消请求(杀死线程)获取、设置线程的取消状态和取消类型压栈、或弹栈线程的取消处理例程1、概念线程实际上是应用层的概念在 Linux 内核中所有的调度实体都被称为任务task他们之间的区别是有些任务自己拥有一套完整的资源而有些任务彼此之间共享一套资源。进程线程的一些区别本质区别进程是操作系统资源分配的基本单位而线程是CPU基本调度单位。进程之间资源是独立的进程之间不能共享数据段一个进程下必然有1个线程同一进程下的多线程之间有一部分资源是共享的。共享的资源进程内存空间同一进程中的所有线程共享该进程的内存包括堆区和数据段全局变量、静态变量等。不同线程可以访问和修改共享的全局变量和动态分配的内存。文件描述符所有线程共享进程打开的文件描述符这意味着它们可以共享文件、网络连接等资源。进程的代码段所有线程执行相同的程序代码。内存映射区域Memory-mapped regions如果有内存映射的文件或共享内存区域线程也可以共享这些内存区域。独立的资源线程栈每个线程都有自己的栈空间用于存储局部变量和函数调用信息。不同线程的栈是相互独立的互不干扰。程序计数器PC每个线程有自己独立的程序计数器用来跟踪该线程当前执行到哪一条指令。寄存器每个线程有自己的寄存器值独立于其他线程。线程局部存储TLS每个线程可以有自己的线程局部存储例如pthread中的thread_local变量这些变量对其他线程是不可见的。健壮性进程健壮性更强同一进程下的多线程有一个线程崩溃整个进程就会崩溃。切换效率不同进程之间的切换效率低于同一进程下的多线程之间切换。2、线程API函数2.1创建线程对此函数的使用需要知道以下几点1线程例程指的是如果线程创建成功那么该线程会立即去执行的函数。2POSIX 线程库的所有 API 对返回值的处理原则都是一致的成功返回 0失败返回错误码 errno。3线程属性如果为 NULL则会创建一个标准属性的线程-l线程库名练习创建线程#include stdio.h #include pthread.h void * Func (void * arg) { while(1) { printf(新创建线程打印输出\n); sleep(1); } } int main(int argc, char const *argv[]) { // int pthread_create(pthread_t *thread, const pthread_attr_t *attr, // void *(*start_routine) (void *), void *arg); pthread_t My_Pth_TID 1; pthread_create(My_Pth_TID,NULL,Func,NULL); while(1) { printf(这里是初始线程打印输出\n); sleep(1); } return 0; }2.2线程属性1.线程创建类型 (pthread_create的行为)属性类型PTHREAD_CREATE_JOINABLE或PTHREAD_CREATE_DETACHED说明此属性指定线程的创建方式。PTHREAD_CREATE_JOINABLE这是默认的设置表示线程在结束后可以被pthread_join()等待。PTHREAD_CREATE_DETACHED表示线程是分离的线程结束后系统自动回收其资源不能被pthread_join()等待。2.线程栈大小 (pthread_attr_setstacksize)属性类型size_t说明设置线程的栈大小。每个线程都有一个栈它是线程执行时的内存区域。如果栈空间不足线程可能会崩溃或出现栈溢出。函数pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)例如pthread_attr_setstacksize(attr, 1024 * 1024);设置线程栈大小为 1MB。3.线程栈地址 (pthread_attr_setstack)属性类型void *说明设置线程栈的起始地址。这通常用于在特定的内存区域分配栈。一般情况下不需要设置除非你有特殊需求比如在嵌入式系统中。函数pthread_attr_setstack(pthread_attr_t *attr, void *stack, size_t stacksize)例如pthread_attr_setstack(attr, stack_buffer, 1024 * 1024);设置栈起始地址为stack_buffer。4.线程调度策略 (pthread_attr_setschedpolicy)属性类型int说明设置线程的调度策略。不同的调度策略决定了操作系统如何安排线程执行。SCHED_FIFO先来先服务调度。SCHED_RR轮转调度。SCHED_OTHER默认调度策略通常是基于时间片的调度策略。函数pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)例如pthread_attr_setschedpolicy(attr, SCHED_FIFO);设置为先来先服务调度。5.线程优先级 (pthread_attr_setschedparam)属性类型struct sched_param说明设置线程的调度优先级。优先级值通常是整数值越大线程的优先级越高。函数pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param)例如struct sched_param param; param.sched_priority 20; pthread_attr_setschedparam(attr, param);设置线程的优先级为 20。6.线程继承调度策略 (pthread_attr_setinheritsched)属性类型int说明设置线程是否继承创建线程的调度属性。PTHREAD_INHERIT_SCHED线程继承创建线程的调度策略和优先级。PTHREAD_EXPLICIT_SCHED线程不继承父线程的调度属性使用自己设置的属性。函数pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched)例如pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED);设置线程不继承父线程的调度策略。7.线程的守护线程属性 (pthread_attr_setdetachstate)属性类型int说明设置线程的分离状态。PTHREAD_CREATE_DETACHED线程是分离的不能被pthread_join()等待。PTHREAD_CREATE_JOINABLE线程是可连接的允许pthread_join()等待线程结束。函数pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)例如pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED);设置线程为分离状态。8.线程的调度优先级范围 (pthread_attr_getschedparam和pthread_attr_getschedpolicy)说明用于获取线程的调度参数和调度策略获取线程属性API设置线程属性API以上 API 都是针对线程属性操作的所谓线程属性是类型为 pthread_attr_t 的变量设置一个线程的属性时通过以上相关的函数接口将需要的属性添加到该类型 变量里面再通过 pthread_create( )的第二个参数来创建相应属性的线程。线程属性变量的使用步骤是1定义线程属性变量并且使用 pthread_attr_init( )初始化。2使用 pthread_attr_setXXX( )来设置相关的属性。3使用该线程属性变量创建相应的线程。4使用 pthread_attr_destroy( )销毁该线程属性变量。主要的几个属性API获取、设置线程的分离属性一条线程如果是可接合的意味着这条线程在退出时不会自动释放自身资源而会成为僵尸线程同时意味着该线程的退出值可以被其他线程获取。因此如果不需要某条线程的退出值的话那么最好将线程设置为分离状态以保证该线程不会成为僵尸线程。以下接口跟线程的调度相关获取、设置线程是否继承创建者的调度策略当需要给一个线程设置调度方面的属性时必须先将线程的inheritsched设置为PTHREAD_EXPLICIT_SCHED。获取、设置线程的调度策略以下是关于调度策略需要知道的几点1当线程的调度策略为 SCHED_FIFO 时其静态优先级static priority必须设置为 1-99这将意味着一旦这种线程处于就绪态时他能立即抢占任何静态优先级为 0 的普通线程。采用 SCHED_FIFO 调度策略的线程还遵循以下规则A) 当他处于就绪态时就会被放入其所在优先级队列的队尾位置。B) 当被更高优先级的线程抢占后他会被放入其所在优先级队列的队头位置当所有优先级比他高的线程不再运行后他就恢复运行。C) 当他调用 sched_yield( )后他会被放入其所在优先级队列的队尾的位置。总的来讲一个具有 SCHED_FIFO 调度策略的线程会一直运行直到发送I/O 请求或者被更高优先级线程抢占或者调用sched_yield( )主动让出 CPU。2当线程的调度策略为 SCHED_RR 时情况跟SCHED_FIFO 是一样的区别在于每一个 SHCED_RR 策略下的线程都将会被分配一个额度的时间片当时间片耗光时他会被放入其所在优先级队列的队尾的位置。可以用 sched_rr_get_interval( )来获得时间片的具体数值。3当线程的调度策略为 SCHED_OTHER 时其静态优先级static priority必须设置为 0。该调度策略是 Linux 系统调度的默认策略处于 0 优先级别的这些线程按照所谓的动态优先级被调度而动态优先级起始于线程的 nice 值且每当一个线程已处于就绪态但被调度器调度无视时其动态优先级会自动增加一个单位这样能保证这些线程竞争CPU 的公平性。线程的静态优先级和动态优先级的设置跟线程的栈和警戒区的大小相关API获取、设置线程栈大小、警戒区大小为什么常常不需要增大栈的空间默认的栈大小通常已经足够。栈溢出问题常常是由于算法设计或代码逻辑问题而不是栈空间不足。增加栈空间会浪费系统资源并不一定能解决根本问题。 因此最好的做法是通过优化代码和算法来避免栈溢出。退出、接合线程线程跟进程类似在缺省的状态下退出之后会变成僵尸线程并且保留退出值。其他 线程可以通过相关 API接合该线程——使其资源被系统回收如果愿意的话还可以顺便获取其退出值。下面是相关 API如果线程退出时没有退出值那么 retval 可以指定为 NULL。pthread_join( )指定的线程如果尚在运行那么他将会阻塞等待。pthread_tryjoin_np( )指定的线程如果尚在运行那么他将会立即出错返回。retval的取值逻辑线程被取消通过pthread_cancelretval会被设置为PTHREAD_CANCELED一个特殊宏通常是(void*)-1。线程正常退出通过return或pthread_exitretval的值是线程函数的返回值或pthread_exit传入的指针。线程未终止未调用pthread_join前调用pthread_join的线程会阻塞直到目标线程结束。测试1、创建一个默认属性的线程主线程等待被创建的线程结束后接合被创建的线程并接收返回值#include stdio.h #include pthread.h #include stdlib.h void * Func (void * arg) { while(1) { printf(新创建线程打印输出\n); sleep(3); break; } //void *calloc(size_t nmemb, size_t size); //注意这里不能创建一个变量进行返回因为当这个线程退出后创建变量的源空间就会销毁。 //因此我们需要再堆上开辟空间返回 int* tetVal_Addr (int*)calloc(1, 4); *tetVal_Addr 115200; printf(新线程准备退出\n); //void pthread_exit(void *retval); pthread_exit((void*)tetVal_Addr);//之后的程序不会被执行 printf(新线程已退出\n); } int main(int argc, char const *argv[]) { // int pthread_create(pthread_t *thread, const pthread_attr_t *attr, // void *(*start_routine) (void *), void *arg); pthread_t My_Pth_TID 1; pthread_create(My_Pth_TID,NULL,Func,NULL); while(1) { printf(这里是初始线程打印输出\n); sleep(1); break; } //接合线程回收线程资源 //阻塞等待接合 //int pthread_join(pthread_t thread, void **retval); int *retval ; pthread_join(My_Pth_TID, (void*)retval); printf(新线程的退出返回值为%d\n,*retval); return 0; }2、将被创建的线程设置为分离属性被创建的线程不需要返回值分离属性的线程无法返回值#include stdio.h #include pthread.h #include stdlib.h #include unistd.h #include errno.h void * Func(void *arg) { while (1) { printf(新创建线程打印输出\n); sleep(3); break; } // 直接打印数值而不是返回指针 int tetVal 115200; printf(新线程准备退出返回值为%d\n, tetVal); // 退出线程 pthread_exit(NULL); // 无需返回指针 } int main(int argc, char const *argv[]) { pthread_attr_t attr; // 初始化线程属性 pthread_attr_init(attr); // 将线程设置为分离属性(线程在退出后可以直接被系统收尸) pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); // 将线程设置为接合属性 //pthread_attr_setdetachstate(attr,PTHREAD_CREATE_JOINABLE); pthread_t My_Pth_TID; pthread_create(My_Pth_TID, attr, Func, NULL); // 销毁线程属性 pthread_attr_destroy(attr); while (1) { printf(这里是初始线程打印输出\n); sleep(10); break; } //分离线程不需要 pthread_join移除以下代码 int *retval ; if (0 pthread_join(My_Pth_TID, (void*)retval)) { printf(新线程的退出接合成功返回值为%d\n,*retval); } else { printf(接合失败\n); } return 0; }给指定线程发送一个取消请求(杀死线程)另外或许在某个时刻不能等某个线程“自然死亡”而需要勒令其马上结束此时可以给线程发送一个取消请求让其中断执行而退出。练习杀掉创建的线程#include stdio.h #include pthread.h #include stdlib.h #include unistd.h void * Func (void * arg) { while(1) { printf(新创建线程打印输出\n); sleep(3); } //void *calloc(size_t nmemb, size_t size); int* tetVal_Addr (int*)calloc(1, 4); *tetVal_Addr 115200; printf(新线程准备退出\n); //void pthread_exit(void *retval); pthread_exit((void*)tetVal_Addr);//之后的程序不会被执行 printf(新线程已退出\n); } int main(int argc, char const *argv[]) { int i 0; // int pthread_create(pthread_t *thread, const pthread_attr_t *attr, // void *(*start_routine) (void *), void *arg); pthread_t My_Pth_TID 1; pthread_create(My_Pth_TID,NULL,Func,NULL); while(1) { printf(这里是初始线程打印输出\n); sleep(1); i ; if(i 10) { printf(我要杀掉创建的进程\n); //杀死创建的进程 pthread_cancel(My_Pth_TID); printf(我要杀掉完成\n); sleep(5); break; } } //如果杀死线程就不能进行接合操作!!!!! //接合线程回收线程资源 //阻塞等待接合 //int pthread_join(pthread_t thread, void **retval); // int *retval ; // pthread_join(My_Pth_TID, (void*)retval); // printf(新线程的退出返回值为%d\n,*retval); return 0; }通过结果我们可知线程被杀掉我们不能去接合突然死掉的线程。获取、设置线程的取消状态和取消类型测试启用取消异步取消#include stdio.h #include pthread.h #include unistd.h #include errno.h // 线程2启用取消 异步取消类型 void* thread_func2(void* arg) { int old_state, old_type; // 获取并设置取消状态虽然默认是启用这里为了演示API使用 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, old_state); // 设置取消类型为异步PTHREAD_CANCEL_ASYNCHRONOUS pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, old_type); printf(Thread2: 旧取消类型: %s\n, (old_type PTHREAD_CANCEL_DEFERRED) ? 延迟 : 异步); // 模拟工作循环没有取消点 while(1) { printf(Thread2: 运行中...\n); sleep(1); // 注意异步取消不需要取消点 // 这里故意不使用sleep等可能产生取消点的函数 } return NULL; } int main() { pthread_t tid1, tid2; // 创建线程2异步取消 if(pthread_create(tid2, NULL, thread_func2, NULL) ! 0) { perror(创建线程2失败); return 1; } sleep(2); // 等待线程运行 // 发送取消请求 printf(\n主线程发送取消请求...\n); pthread_cancel(tid2); // 等待线程结束 void* ret1, *ret2; pthread_join(tid2, ret2); printf(线程2退出状态: %s\n, (ret2 PTHREAD_CANCELED) ? 被取消 : 正常退出); return 0; }2.禁用取消延时取消#include stdio.h #include pthread.h #include unistd.h #include errno.h // 线程1禁用取消请求 延迟取消默认类型虽然实际不会执行取消 void* thread_func1(void* arg) { int old_state; // 设置取消状态为禁用PTHREAD_CANCEL_DISABLE pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, old_state); printf(Thread1: 旧取消状态: %s\n, (old_state PTHREAD_CANCEL_ENABLE) ? 启用 : 禁用); // 模拟工作循环 for(int i 0; i 5; i) { printf(Thread1: 工作中...(%d/5)\n, i1); sleep(1); } // 重新启用取消 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, old_state); printf(Thread1: 完成工作退出\n); return NULL; } // 线程2启用取消 异步取消类型 void* thread_func2(void* arg) { int old_state, old_type; // 获取并设置取消状态虽然默认是启用这里为了演示API使用 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, old_state); // 设置取消类型为异步PTHREAD_CANCEL_ASYNCHRONOUS pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, old_type); printf(Thread2: 旧取消类型: %s\n, (old_type PTHREAD_CANCEL_DEFERRED) ? 延迟 : 异步); // 模拟工作循环没有取消点 while(1) { printf(Thread2: 运行中...\n); sleep(1); // 注意异步取消不需要取消点 // 这里故意不使用sleep等可能产生取消点的函数 } return NULL; } int main() { pthread_t tid1, tid2; // 创建线程1禁用取消 if(pthread_create(tid1, NULL, thread_func1, NULL) ! 0) { perror(创建线程1失败); return 1; } // // 创建线程2异步取消 // if(pthread_create(tid2, NULL, thread_func2, NULL) ! 0) { // perror(创建线程2失败); // return 1; // } sleep(2); // 等待线程运行 // 发送取消请求 printf(\n主线程发送取消请求...\n); pthread_cancel(tid1); // pthread_cancel(tid2); // 等待线程结束 void* ret1, *ret2; pthread_join(tid1, ret1); // pthread_join(tid2, ret2); printf(\n线程1退出状态: %s\n, (ret1 PTHREAD_CANCELED) ? 被取消 : 正常退出); // printf(线程2退出状态: %s\n, // (ret2 PTHREAD_CANCELED) ? 被取消 : 正常退出); return 0;压栈、或弹栈线程的取消处理例程练习1. 一个线程函数使用pthread_cleanup_push注册清理函数。2. 清理函数的具体实现如关闭文件或释放内存。3. 主线程创建子线程并在适当时候取消它。4. 使用pthread_cleanup_pop弹出清理处理程序可能带参数执行或不执行。5. 验证资源是否被正确释放无论线程是被取消还是正常退出。#include stdio.h #include pthread.h #include unistd.h #include stdlib.h /* 模拟资源结构体 */ typedef struct { FILE* logfile; char* buffer; } ThreadResource; // 清理函数1关闭文件 void cleanup_file(void* arg) { FILE** fp (FILE**)arg; if (*fp) { fclose(*fp); printf(清理处理: 已关闭文件\n); *fp NULL; } } // 清理函数2释放内存 void cleanup_memory(void* arg) { char** buffer (char**)arg; if (*buffer) { free(*buffer); printf(清理处理: 已释放内存\n); *buffer NULL; } } void* thread_function(void* arg) { ThreadResource res {NULL, NULL}; /* 压栈清理处理程序FILO顺序 */ pthread_cleanup_push(cleanup_file, res.logfile); pthread_cleanup_push(cleanup_memory, res.buffer); /* 关键代码段开始 */ printf(线程: 正在打开日志文件\n); res.logfile fopen(thread_log.txt, w); if (!res.logfile) { perror(fopen失败); pthread_exit(NULL); } printf(线程: 正在分配缓冲区\n); res.buffer (char*)malloc(1024); if (!res.buffer) { perror(malloc失败); pthread_exit(NULL); } /* 模拟耗时操作可能被取消*/ for (int i 0; i 5; i) { printf(线程: 正在工作(%d/5)\n, i1); sleep(1); // 这是一个取消点 } /* 关键代码段结束 */ /* 弹栈清理处理程序0表示不执行*/ pthread_cleanup_pop(0); // 弹出内存清理但不执行 pthread_cleanup_pop(0); // 弹出文件清理但不执行 /* 正常释放资源 */ free(res.buffer); fclose(res.logfile); printf(线程: 正常完成资源释放\n); return NULL; } int main() { pthread_t tid; ThreadResource resource {NULL, NULL}; if (pthread_create(tid, NULL, thread_function, resource) ! 0) { perror(pthread_create失败); return 1; } sleep(2); // 等待线程进入工作状态 printf(\n主线程: 发送取消请求\n); pthread_cancel(tid); void* retval; pthread_join(tid, retval); if (retval PTHREAD_CANCELED) { printf(\n线程已被成功取消\n); } else { printf(\n线程正常退出\n); } return 0; }