学院网站设计流程,网站的二级网页关键词,领动做的企业网站怎么样,百度用户服务中心人工电话进程组和会话
概念和特性
进程组#xff0c;也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念#xff0c;是为了简化对多个进程的管…进程组和会话
概念和特性
进程组也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念是为了简化对多个进程的管理。
当父进程创建子进程的时候默认子进程与父进程属于同一进程组。进程组ID第一个进程ID(组长进程)。所以组长进程标识其进程组ID其进程ID
可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死。
组长进程可以创建一个进程组创建该进程组中的进程然后终止。只要进程组中有一个进程存在进程组就存在与组长进程是否终止无关。
进程组生存期进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
一个进程可以为自己或子进程设置进程组ID
会话概念
会话Session是操作系统中的一个概念用于表示一个用户与系统之间的一系列交互活动。它是一个逻辑上的分组用于管理用户的登录状态、控制终端、进程组以及与这些进程相关的环境和属性。 会话的主要作用是提供一个框架使得用户可以在一个隔离的环境中执行程序同时允许系统对这些程序进行统一的管理。 一般是包括多个进程组的集合。 例如一个浏览器与Web服务器之间连续发生的一系列请求和响应的过程
新会话进程脱离于终端不随用户注销消亡也作为守护进程 主要还是为了服务于一次终端与用户的会话搭建的框架用于统一的资源调度。 会话创建
创建一个会话需要注意以下6点注意事项
若要启动一个新的会话当前进程不能是其所属进程组的组长。启动后该进程将成为新会话的首进程。创建新会话的进程将自动成为新进程组的组长。需有root权限 (ubuntu不需要)新创建的会话将不再与任何控制终端关联因此该会话将没有控制终端。如果一个进程在尝试创建新会话时已经是其进程组的组长那么这个操作将不会成功并且会返回一个错误。在创建新会话的过程中首先需要调用fork函数创建子进程然后父进程结束运行子进程继续执行setsid函数。确保新会话的创建不会受到原有进程环境的影响。 [!info] setsid后的IO变化 当一个进程调用 setsid 后它将失去与控制终端的联系并且它的标准输入、输出和错误流stdin、stdout、stderr将不再与任何终端相关联。这意味着
标准输入stdin进程将不再有控制终端作为其标准输入。如果进程尝试从 stdin 读取它将收到一个 EOF文件结束符因为没有任何终端与之关联。
标准输出stdout和标准错误stderr这两个流的行为取决于进程的设置。默认情况下如果进程没有重定向这些流到其他文件或设备它们将被重定向到 /dev/null这是一个特殊的文件它会丢弃所有写入其中的数据。这意味着任何尝试写入 stdout 或 stderr 的输出都将丢失。
一保险起见一般需要自己手动重定向到null
getsid 函数
获取进程所属的会话ID
pid_t getsid(pid_t pid);
// 成功返回调用进程的会话ID失败-1设置errnopid为0表示察看当前进程session ID ps ajx命令查看系统中的进程。参数a表示不仅列当前用户的进程也列出所有其他用户的进程参数x表示不仅列有控制终端的进程也列出所有无控制终端的进程参数j表示列出与作业控制相关的信息。 组长进程不能成为新会话首进程新会话首进程必定会成为组长进程。
setsid函数
创建一个会话并以自己的ID设置进程组ID同时也是新会话的ID。
调用setsid()创建一个新的会话并成为该会话的首进程。这将使进程脱离任何已存在的终端和进程组也就是成为守护进程不随终端注销。
标准输入输出所对应的文件描述符为空未使用一般重定向标准输入输出stdin、stdout、stderr所对应的fd 0 1 2到/dev/null或特定的日志文件。
pid_t setsid(void);
//成功返回调用进程的会话ID失败-1设置errno调用了setsid函数的进程既是新的会长也是新的组长。 [!info] 练习 fork一个子进程并使其创建一个新会话。查看进程组ID、会话ID前后变化 #include stdio.h
#include stdlib.h
#include wait.hint main() {pid_t pid;pidfork();if(pid0){perror(fork);exit(1);}else if(pid0){printf(chile process PID is %d\n,getpid());printf(Group ID of child is %d\n, getpgid(0));printf(Session ID of child is %d\n, getsid(0));sleep(1); setsid();// 子进程成为新会话首进程且成为组长进程。printf(Changed:\n);printf(chile process PID is %d\n,getpid());printf(Group ID of child is %d\n, getpgid(0));printf(Session ID of child is %d\n, getsid(0));sleep(1);exit(0);}return 0;
}守护进程
Daemon(精灵)进程是Linux中的后台服务进程通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。
Linux后台的一些系统服务进程没有控制终端不能直接和用户交互。不受用户登录、注销的影响一直在运行着他们都是守护进程。如预读入缓输出机制的实现ftp服务器nfs服务器等。
创建守护进程最关键的一步是调用setsid函数创建一个新的Session并成为Session Leader。
创建守护进程模型
创建子进程父进程退出 所有工作在子进程中进行形式上脱离了控制终端在子进程中创建新会话 setsid()函数 使子进程完全独立出来脱离控制改变当前目录为根目录 chdir()函数 防止占用可卸载的文件系统 也可以换成其它路径重设文件权限掩码 umask()函数 防止继承的文件创建屏蔽字拒绝某些权限 增加守护进程灵活性关闭文件描述符 继承的打开文件不会用到浪费系统资源无法卸载开始执行守护进程核心工作守护进程退出处理程序模型
守护进程创建实例
#include stdio.h
#include stdlib.h
#include wait.h
#include sys/stat.h
#include fcntl.hvoid err(const char *str) {perror(str);exit(1);
}int main() {// 创建不是进程组首进程的进程pid_t pid;pid fork();if (pid 0)exit(0);// 创建新会话进程脱离原进程组,成为会话首进程if (setsid() -1)err(setsid error);// 改变目录位置if (chdir(/home/qiuliw/vscode) -1)err(chdir error);// 改变默认文件访问权限屏蔽组和其他用户写权限umask(0022);// 重定向文件描述符 0 1 2 到 /dev/nullint fd;close(STDIN_FILENO);fd open(/dev/null, O_RDWR);if (fd -1)err(open error);dup2(fd,STDOUT_FILENO);dup2(fd,STDERR_FILENO);// 模拟守护进程业务while(1){sleep(1);}return 0;
}线程概念
什么是线程
LWPlight weight process 轻量级的进程本质仍是进程(在Linux环境下)
进程独立地址空间拥有PCB 线程有独立的PCB但没有独立的地址空间(共享) 区别在于是否共享地址空间。 独居(进程)合租(线程)。 Linux下 线程最小的执行单位 进程最小分配资源单位可看成是只有一个线程的进程。
Linux内核线程实现原理
类Unix系统中早期是没有“线程”概念的80年代才引入借助进程机制实现出了线程的概念。因此在这类系统中进程和线程关系密切。
轻量级进程(light-weight process)也有PCB创建线程使用的底层函数和进程一样都是clone从内核里看进程和线程是一样的都有各自不同的PCB但是PCB中指向内存资源的三级页表是相同的进程可以蜕变成线程线程可看做寄存器和栈的集合在linux下线程最是小的执行单位进程是最小的分配资源单位 察看LWP号ps –Lf pid 查看指定线程的lwp号。 三级映射进程PCB -- 页目录(可看成数组首地址位于PCB中) -- 页表 -- 物理页面 -- 内存单元 参考《Linux内核源代码情景分析》 ----毛德操 对于进程来说相同的地址(同一个虚拟地址)在不同的进程中反复使用而不冲突。原因是他们虽虚拟址一样但页目录、页表、物理页面各不相同。相同的虚拟址映射到不同的物理页面内存单元最终访问不同的物理页面。 但线程不同两个线程具有各自独立的PCB但共享同一个页目录也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。 实际上无论是创建进程的fork还是创建线程的pthread_create底层实现都是调用同一个内核函数clone。 如果复制对方的地址空间那么就产出一个“进程”如果共享对方的地址空间就产生一个“线程”。 因此Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以线程所有操作函数 pthread_* 是库函数而非系统调用。
线程共享资源
文件描述符表每种信号的处理方式当前工作目录用户ID和组ID内存地址空间 (.text/.data/.bss/heap/共享库)
线程非共享资源
线程id处理器现场和栈指针(内核栈)独立的栈空间(用户空间栈)errno变量信号屏蔽字调度优先级
线程优、缺点
优点 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便 缺点 1. 库函数不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好 优点相对突出缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。
线程控制原语
pthread_self函数
获取线程ID。其作用对应进程中 getpid() 函数。
pthread_t pthread_self(void);
//返回值成功0 失败无线程IDpthread_t类型本质在Linux下为无符号整数(%lu)其他系统中可能是结构体实现
线程ID是进程内部识别标志。(两个进程间线程ID允许相同)
注意不应使用全局变量 pthread_t tid在子线程中通过pthread_create传出参数来获取线程ID而应使用pthread_self。
pthread_create函数
创建一个新线程。 其作用对应进程中fork() 函数。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);//返回值成功0 //失败错误号 -----Linux环境下所有线程特点失败均直接返回错误号。pthread_t当前Linux中可理解为
typedef unsigned long int pthread_t;参数
thread传出参数保存系统为我们分配好的线程IDattr通常传NULL表示使用线程默认属性。若想使用具体属性也可以修改该参数。void *(*start_routine) (void *)函数指针指向线程主函数(线程体)该函数运行结束则线程结束。arg 线程主函数执行期间所使用的参数。
在一个线程中调用pthread_create()创建新的线程后当前线程从pthread_create()返回继续往下执行而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数是通过pthread_create的arg参数传递给它的该参数的类型为void *这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *这个指针的含义同样由调用者自己定义。start_routine返回时这个线程就退出了其它线程可以调用pthread_join得到start_routine的返回值类似于父进程调用wait(2)得到子进程的退出状态稍后详细介绍pthread_join。 pthread_create成功返回后新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t每个进程的id在整个系统中是唯一的调用getpid(2)可以获得当前进程的id是一个正整数值。线程id的类型是thread_t它只在当前进程中保证是唯一的在不同的系统中thread_t这个类型有不同的实现它可能是一个整数值也可能是一个结构体也可能是一个地址所以不能简单地当成整数用printf打印调用pthread_self(3)可以获得当前线程的id。 attr参数表示线程属性本节不深入讨论线程属性所有代码例子都传NULL给attr参数表示线程属性取缺省值感兴趣的读者可以参考APUE。 [!info] 练习 创建一个新线程打印线程ID。注意链接线程库 -lpthread #include stdio.h
#include stdlib.h
#include pthread.h// 线程函数
void* thread_function(void* arg) {// 获取并打印线程IDpthread_t thread_id pthread_self();printf(Thread ID: %ld\n, (long)thread_id);return NULL;
}int main() {pthread_t thread_id;// 创建线程if (pthread_create(thread_id, NULL, thread_function, NULL) ! 0) {perror(pthread_create);return EXIT_FAILURE;}// 等待线程结束if (pthread_join(thread_id, NULL) ! 0) {perror(pthread_join);return EXIT_FAILURE;}return EXIT_SUCCESS;
}由于pthread_create的错误码不保存在errno中因此不能直接用perror(3)打印错误信息可以先用strerror(3)把错误码转换成错误信息再打印。如果任意一个线程调用了exit或_exit则整个进程的所有线程都终止由于从main函数return也相当于调用exit为了防止新创建的线程还没有得到执行就终止我们在main函数return之前延时1秒这只是一种权宜之计即使主线程等待1秒内核也不一定会调度新创建的线程执行下一节我们会看到更好的办法。 [!info] 练习 循环创建多个线程每个线程打印自己是第几个被创建的线程。(类似于进程循环创建子进程) #include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include pthread.hvoid sys_err(const char *str){perror(str);exit(1);
}void *tfn(void *arg){int n *((int *)arg);sleep(n);printf(Im %dth thread: pid %d, tid %lu\n,n1,getpid(),pthread_self());return NULL;
}int main(){int i,ret;pthread_t tid;int *n;for(i0;i5;i){n (int *)malloc(sizeof(int));*n i;ret pthread_create(tid,NULL,tfn,n);if(ret ! 0){sys_err(pthread_create error);}}sleep(i); // 防止主线程返回内核执行exit销毁进程printf(Im %dth thread: pid %d, tid %lu\n,i1,getpid(),pthread_self());return 0;
}for循环的不同轮次内创建的同名变量使用相同的地址 拓展思考将pthread_create函数参4修改为(void *)i, 将线程主函数内改为i*((int *)arg)是否可以 线程与共享 线程间共享全局变量 【牢记】线程默认共享数据段、代码段等地址空间常用的是全局变量。而进程不共享全局变量只能借助mmap。
pthread_exit函数
将单个线程退出
void pthread_exit(void *retval);
//参数retval表示线程退出状态通常传NULL思考使用exit将指定线程退出可以吗
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include pthread.hvoid sys_err(const char *str){perror(str);exit(1);
}void *tfn(void *arg){int n *((int *)arg);if(n2)exit(0); // 退出进程sleep(n);printf(Im %dth thread: pid %d, tid %lu\n,n1,getpid(),pthread_self());return NULL;
}int main(){int i,ret;pthread_t tid;int *n;for(i0;i5;i){n (int *)malloc(sizeof(int));*n i;ret pthread_create(tid,NULL,tfn,n);if(ret ! 0){sys_err(pthread_create error);}}sleep(i); // 防止主线程返回内核执行exit销毁进程printf(Im %dth thread: pid %d, tid %lu\n,i1,getpid(),pthread_self());return 0;
}结论线程中禁止使用exit函数会导致进程内所有线程全部退出。 在不添加sleep控制输出顺序的情况下。pthread_create在循环中几乎瞬间创建5个线程但只有第1个线程有机会输出或者第2个也有也可能没有取决于内核调度如果第3个线程执行了exit将整个进程退出了所以全部线程退出了。 所以多线程环境中应尽量少用或者不使用exit函数取而代之使用pthread_exit函数将单个线程退出。任何线程里exit导致进程退出其他线程未工作结束主控线程退出时不能return或exit。
另注意pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的不能在线程函数的栈上分配因为当其它线程得到这个返回指针时线程函数已经退出了。
【练习】编写多线程程序总结exit、return、pthread_exit各自退出效果。 return返回到调用者那里去。非主函数线程return不会导致进程结束。主函数线程return会直接通知内核立即调用exit回收这个进程只有main函数线程与内核回收钩子绑定。 pthread_exit()将调用该函数的线程退出 exit: 将进程退出。
pthread_join函数
阻塞等待线程退出获取线程退出状态 其作用对应进程中 waitpid() 函数。
int pthread_join(pthread_t thread, void **retval);
// 成功0失败错误号参数thread线程ID 【注意】不是指针retval存储线程结束状态。返回值是指针所以需要传存储指针的地址也就是二级指针 对比记忆 进程中main返回值、exit参数–int等待子进程结束 wait 函数参数–int * 线程中线程主函数返回值、pthread_exit–void *等待线程结束 pthread_join 函数参数–void ** [!info] 练习 参数 retval 非空用法。 #include stdio.h
#include stdlib.h
#include string.h
#include pthread.hstruct thrd{int var;char str[256];
};void *tfn(void * arg){struct thrd *tval;tval (struct thrd *) malloc(sizeof(struct thrd));tval-var 100;strcpy(tval-str,hello thread);return (void *)tval;
}void sys_err(const char *str){perror(str);exit(1);
}int main(int argc,char *argv[]){pthread_t tid;int ret;struct thrd *retval;pthread_create(tid,NULL,tfn,NULL);if(ret! 0){sys_err(pthread_create error);}ret pthread_join(tid,(void **)retval);if(ret !0)sys_err(pthread_join error);printf(child thread exit with var %d, str %s\n,retval-var,retval-str);pthread_exit(NULL);return 0;
}调用该函数的线程将挂起等待直到id为thread的线程终止。thread线程以不同的方法终止通过pthread_join得到的终止状态是不同的总结如下
如果thread线程通过return返回retval所指向的单元里存放的是thread线程函数的返回值。如果thread线程被别的线程调用pthread_cancel异常终止掉retval所指向的单元里存放的是常数PTHREAD_CANCELED。如果thread线程是自己调用pthread_exit终止的retval所指向的单元存放的是传给pthread_exit的参数。如果对thread线程的终止状态不感兴趣可以传NULL给retval参数。 [!info] 练习线程返回常量与类型转换 #include stdio.h
#include stdlib.h
#include string.h
#include pthread.hvoid *tfn(void * arg){return (void *)74;
}void sys_err(const char *str){perror(str);exit(1);
}int main(int argc,char *argv[]){pthread_t tid;int ret;ret pthread_create(tid,NULL,tfn,NULL);int retval;if(ret! 0){sys_err(pthread_create error);}// pthread_join会把返回值存入retval,所以传入一个retval。只要接收变量的空间和返回值空间长度一样即可不用管类型函数内部自动对形参解引用然后装入返回值。参数强转(void **),返回值强转(void *)。ret pthread_join(tid,(void **)retval);if(ret !0)sys_err(pthread_join error);printf(child thread exit with var %d\n,retval);pthread_exit(NULL);return 0;
}[!info] 练习 使用pthread_join函数将循环创建的多个子线程回收。 #include stdio.h
#include stdlib.h
#include string.h
#include pthread.h
#define NUMS 5void *tfn(void *arg) {int n *(int*)arg;printf(I‘m thread %dth ...\n,n);pthread_exit( (void*)n);
}void sys_err(const char *str) {perror(str);exit(1);
}int main(int argc, char *argv[]) {pthread_t tid[NUMS];int arg[NUMS];for (int i 0; i NUMS; i) {arg[i] i1;if (pthread_create(tid i, NULL, tfn, arg i) ! 0)sys_err(pthread_create error);}for(int i0;iNUMS;i){int ret[NUMS];pthread_join(tid[i],(void**)(reti));printf(已回收线程 tid %d, ret %d\n,tid[i],ret[i]);}printf(回收完成\n);return 0;
}pthread_detach函数
实现线程分离
int pthread_detach(pthread_t thread);
//成功0失败错误号线程分离状态指定该状态线程主动与主控线程断开关系。线程结束后其退出状态不由其他线程获取而直接自己自动释放。网络、多线程服务器常用。
进程若有该机制将不会产生僵尸进程。僵尸进程的产生主要由于进程死后大部分资源被释放一点残留资源仍存于系统中导致内核认为该进程仍存在。
也可使用 pthread_create函数参2(线程属性)来设置线程分离。
【练习】使用pthread_detach函数实现线程分离
#include stdio.h
#include stdlib.h
#include string.h
#include pthread.h
#include signal.hvoid *tfn1(void *arg) {printf(Hello Im thread: pid %d, tid %lu ...\n\n,getpid(),pthread_self());return NULL;
}int main(int argc, char *argv[]) {pthread_t tid;int ret;ret pthread_create(tid,NULL,tfn1,NULL);if(ret !0)perror(pthread_create error);ret pthread_detach(tid);if(ret !0)perror(pthread_detach error);printf(main: pid %d, tid %lu ...\n,getpid(),pthread_self());sleep(2);return 0;
}一般情况下线程终止后其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态这样的线程一旦终止就立刻回收它占用的所有资源而不保留终止状态。
不能对一个已经处于detach状态的线程调用pthread_join这样的调用将返回EINVAL错误。
也就是说如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
检测出错返回
fprintf(stderr,xxx error: %s\n,strerror(ret))pthread_cancel函数
杀死(取消)线程。其作用对应进程中 kill() 函数。
int pthread_cancel(pthread_t thread); 成功0失败错误号简单使用
c
#include stdio.h
#include stdlib.h
#include string.h
#include pthread.h
#include signal.hvoid *tfn(void *arg) {while (1) {printf(thread: pid %d, tid %lu\n, getpid(), pthread_self());sleep(1);}return NULL;
}int main(int argc, char *argv[]) {pthread_t tid;int ret pthread_create(tid, NULL, tfn, NULL);if (ret ! 0){fprintf(stderr,pthread_create error:%s\n, strerror(ret));exit(1);}printf(main: pid %d, tid %lu\n,getpid(),pthread_self());sleep(5);pthread_cancel(tid); // 终止线程return 0;
}[!warning] 注意 线程的取消并不是实时的而有一定的延时。需要等待线程到达某个取消点(检查点)。 类似于玩游戏存档必须到达指定的场所(存档点如客栈、仓库、城里等)才能存储进度。杀死线程也不是立刻就能完成必须要到达取消点。
取消点是线程检查是否被取消并按请求进行动作的一个位置。通常是一些系统调用creatopenpauseclosereadwrite… 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。 可粗略认为一个系统调用(进入内核)即为一个取消点。 如线程中没有取消点可以通过调用pthread_testcancel函数自行设置一个取消点。
返回值被取消的线程退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义#define PTHREAD_CANCELED ((void *) -1)。 因此当我们对一个已经被取消的线程使用pthread_join回收时得到的返回值为 -1 。 [!info] 练习 终止线程的三种方法。注意“取消点”的概念。 #include stdio.h
#include stdlib.h
#include string.h
#include pthread.h
#include signal.hvoid *tfn1(void *arg) {printf(thread 1 returning\n);return (void *)111;
}void *tfn2(void *arg){printf(thread 2 exiting\n);pthread_exit((void*)222);
}void *tfn3(void *arg){while(1){printf(thread 3: Im going to die in 3 second\n);sleep(1); // pthread_testcancel(); // 自己添加取消点return (void *)666;}
}int main(int argc, char *argv[]) {pthread_t tid;void *tret NULL;pthread_create(tid,NULL,tfn1,NULL);pthread_join(tid,tret);printf(thread 1 exit code %d\n\n,tret);pthread_create(tid,NULL,tfn2,NULL);pthread_join(tid,tret);printf(thread 2 exit code %d\n\n,tret);pthread_create(tid,NULL,tfn3,NULL);sleep(3);pthread_cancel(tid);pthread_join(tid,tret);printf(thread 3 exit code %d\n,tret);return 0;
}终止线程方式
总结终止某个线程而不终止整个进程有三种方法
从线程主函数return。这种方法对主控线程不适用从main函数return相当于调用exit。一个线程可以调用pthread_cancel终止同一进程中的另一个线程。线程可以调用pthread_exit终止自己。
控制原语对比
进程 线程
fork pthread_create
exit pthread_exit
wait pthread_join
kill pthread_cancel
getpid pthread_self 命名空间线程属性
本节作为指引性介绍linux下线程的属性是可以根据实际项目需要进行设置之前我们讨论的线程都是采用线程的默认属性默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出更高的要求那么需要设置线程属性比如可以通过设置线程栈的大小来降低内存的使用增加最大线程个数。
typedef struct {int etachstate; //线程的分离状态int schedpolicy; //线程调度策略struct sched_param schedparam; //线程的调度参数int inheritsched; //线程的继承性int scope; //线程的作用域size_t guardsize; //线程栈末尾的警戒缓冲区大小int stackaddr_set; //线程的栈设置void* stackaddr; //线程栈的位置size_t stacksize; //线程栈的大小
} pthread_attr_t; 主要结构体成员 1. 线程分离状态 2. 线程栈大小默认平均分配 3. 线程栈警戒缓冲区大小位于栈末尾 参 APUE.12.3 线程属性 属性值不能直接设置须使用相关函数进行操作初始化的函数为pthread_attr_init这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。 线程属性主要包括如下属性作用域scope、栈尺寸stack size、栈地址stack address、优先级priority、分离的状态detached state、调度策略和参数scheduling policy and parameters。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
线程属性初始化
注意应先初始化线程属性再pthread_create创建线程 初始化线程属性
int pthread_attr_init(pthread_attr_t *attr);
//成功0失败错误号 销毁线程属性所占用的资源
int pthread_attr_destroy(pthread_attr_t *attr);
//成功0失败错误号 线程的分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。 非分离状态线程的默认属性是非分离状态这种情况下原有的线程等待创建的线程结束。只有当pthread_join()函数返回时创建的线程才算终止才能释放自己占用的系统资源。 分离状态分离线程没有被其他的线程所等待自己运行结束了线程也就终止了马上释放系统资源。应该根据自己的需要选择适当的分离状态。
设置线程属性分离or非分离
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 获取程属性分离or非分离
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);attr已初始化的线程属性detachstate PTHREAD_CREATE_DETACHED分离线程PTHREAD_CREATE_JOINABLE非分离线程
这里要注意的一点是如果设置一个线程为分离线程而这个线程运行又非常快它很可能在pthread_create函数返回之前就终止了它终止以后就可能将线程号和系统资源移交给其他的线程使用这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数让这个线程等待一会儿留出足够的时间让函数pthread_create返回。设置一段等待时间是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数它们是使整个进程睡眠并不能解决线程同步的问题。
#include stdio.h
#include stdlib.h
#include string.h
#include pthread.h
#include signal.hvoid *tfn1(void *arg) {printf(Hello Im thread: pid %d, tid %lu ...\n\n, getpid(), pthread_self());return NULL;
}int main(int argc, char *argv[]) {pthread_t tid;int ret;// 初始化线程属性pthread_attr_t attr;ret pthread_attr_init(attr);if (ret ! 0)fprintf(stderr, pthread_attr_init attr:%s\n, strerror(ret));// 设置线程 分离状态属性 为 分离ret pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED);if (ret ! 0)fprintf(stderr, pthread_attr_setdetachstate attr:%s\n, strerror(ret));// 创建线程,传入线程属性pthread_create(tid, attr, tfn1, NULL);if (ret ! 0)perror(pthread_create error);// 销毁线程属性所占用的资源ret pthread_attr_destroy(attr);if (ret ! 0)fprintf(stderr, pthread_attr_destroy attr:%s\n, strerror(ret));// 检测是否分离分离态无法被回收ret pthread_join(tid, NULL);if (ret ! 0){fprintf(stderr, pthread_join attr:%s\n, strerror(ret));exit(1);}// ...主函数任务代码...printf(main: pid %d, tid %lu ...\n, getpid(), pthread_self());pthread_exit((void *) 0);
}线程属性控制示例
#include pthread.h#define SIZE 0x100000
void *th_fun(void *arg)
{while (1) sleep(1);
}
int main(void)
{pthread_t tid;int err, detachstate, i 1;pthread_attr_t attr;size_t stacksize;void *stackaddr;pthread_attr_init(attr); pthread_attr_getstack(attr, stackaddr, stacksize);pthread_attr_getdetachstate(attr, detachstate);if (detachstate PTHREAD_CREATE_DETACHED)printf(thread detached\n);else if (detachstate PTHREAD_CREATE_JOINABLE)printf(thread join\n);elseprintf(thread unknown\n);pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED);while (1) {stackaddr malloc(SIZE);if (stackaddr NULL) {perror(malloc);exit(1);}stacksize SIZE;pthread_attr_setstack(attr, stackaddr, stacksize);err pthread_create(tid, attr, th_fun, NULL);if (err ! 0) {printf(%s\n, strerror(err));exit(1);}printf(%d\n, i);}pthread_attr_destroy(attr);return 0;
}
线程使用注意事项
主线程退出其他线程不退出主线程应调用pthread_exit避免僵尸线程 pthread_join pthread_detach pthread_create指定分离属性 被join线程可能在join函数返回前就释放完自己的所有内存资源所以不应当返回被回收线程栈中的值;malloc和mmap申请的内存可以被其他线程释放应避免在多线程模型中调用fork,除非马上exec子进程中只有调用fork的线程存在其他线程在子进程中均pthread_exit信号的复杂语义很难和多线程共存应避免在多线程引入信号机制