Linux:进程与线程(详)

    xiaoxiao2022-07-05  208

    守护进程性质

    1.属于后台服务进程 2.独立于控制终端 3.周期性执行某任务 4.不受登录用户注销影响 5.一般采用以d结尾的名字

    进程组

    组长:组里边的第一进程 进程组ID == 进程组组长的ID

    会话(多个进程组)

    创建会话的注意事项: 1.不能时进程组长创建 2.创建会话的进程成为新进程组的组长 3.创建出的会话会抛弃原有的控制终端 4.一般步骤:fork(),父进程死,子进程创建会话 获取进程所属会话的ID: pid_t getsid(pid_t pid); 创建一个会话: pid_t setsid(void);

    创建守护进程的模型

    1.fork子进程,父进程退出(必要) 2.子进程创建新会话(必要) setsid(); 3.改变当前工作目录chdir 4.重设文件掩码 增加子进程程序操作的灵活性 umask(0); 5.关闭文件描述符 close(0) close(1) close(2) 释放资源 6.执行核心工作(必要)

    //setsid.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <signal.h> int main(int argc,const char *argv[]) { //创建一个会话 //子进程->会长 pid_t pid = fork(); if(pid > 0) { exit(1); #if 0 kill(getpid(),SIGKILL); raise(SIGKILL); abort(); #endif } else if(pid == 0) { //变成会长,会长就是一个守护进程 setsid(); //让子进程一直活着 while(1); } return 0; }

    运行结果: 从右侧输出可以看出,a.out进程没有输出,也没有结束,而是转到后台运行,抛弃了当前控制终端。此时执行ps aux命令,如下图,即可发现a.out进程依然处于活动状态。 例:写一个守护进程,每隔2S获取一次系统时间,将这个时间写入磁盘文件 NO.1:创建守护进程 NO.2:一个定时器,2S触发一次 setitimer sleep NO.3:信号捕捉

    //process_work.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> #include <string.h> #include <signal.h> #include <time.h> #include <fcntl.h> void dowork(int no) { time_t curtime; //获取当前时间 time(&curtime); //时间格式化 char* ptr = ctime(&curtime); //将时间写入文件 int fd = open("/home/huoz/temp.txt",O_CREAT | O_WRONLY | O_APPEND,0664); write(fd,ptr,strlen(ptr) + 1); close(fd); } int main(int argc,const char *argv[]) { pid_t pid = fork(); if(pid > 0) { //父进程退出 exit(1); } else if(pid == 0) { //子进程创建会话,脱离控制终端。守护进程 setsid(); //改变工作目录 chdir("/home/huoz"); //重置文件掩码 umask(0); //关闭文件描述符 close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); //执行核心操作 //注册信号捕捉 struct sigaction act; act.sa_flags = 0; act.sa_handler = dowork; sigemptyset(&act.sa_mask); sigaction(SIGALRM,&act,NULL); //创建一个定时器 struct itimerval val; //第一次触发的时间 val.it_value.tv_usec = 0; val.it_value.tv_sec = 2; //循环周期 val.it_interval.tv_usec = 0; val.it_interval.tv_sec = 1; setitimer(ITIMER_REAL,&val,NULL); //保持子进程处于活动状态 while(1); } return 0; }

    运行结果: (将process_work.c编译运行后,利用cd ..命令退出当前目录,再用ls命令查看当前目录下的文件,可找到temp.txt文件,利用cat temp.txt命令打开文件可看到右侧第三个终端的结果)

    一张图理解线程:

    主线程和子线程

    共享:

    .text .bss .data 堆 动态加载区 环境变量 命令行参数 通信时,可使用全局变量或堆数据

    不共享:

    栈区是每个线程独有的

    在Linux下,线程就是进程,属于轻量级进程。对于内核来说,线程就是进程。 多进程和多线程的区别: 多进程: -------始终共享的资源:

    代码 文件描述符 内存映射区 mmap

    -------线程共享:

    堆 全局变量

    ------线程节省资源

    创建线程

    int pthread_create( pthread_t* pthread, 线程ID = 无符号长整形 const pthread_attr_t *attr, //线程属性 void * (*start_routine) (void *), //线程处理函数 void *arg //线程处理函数参数 );

    返回值:成功返回0,失败返回错误号 参数: thread:传出参数,线程创建成功后,会被设置一个合适的值 attr:默认传NULL start_routine:子线程的处理函数 arg:回调函数的参数

    //pthread_create.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <string.h> #include <pthread.h> void* myfunc(void* arg) { //打印子线程ID printf("child thread id = %lu\n",pthread_self()); return NULL; } int main(int argc,const char *argv[]) { //创建一个子线程 //线程id变量 pthread_t pthid; pthread_create(&pthid,NULL,myfunc,NULL); printf("parent thread id = %lu\n",pthread_self()); for(int i = 0;i < 5;i++) { printf("%d\n",i); } sleep(2); return 0; }

    运行结果:

    循环创建多个子线程

    //loop_pthread_create.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <string.h> #include <pthread.h> int count = 0; void* myfunc(void* arg) { //取数据 //int num = *(int*)arg; int num = (int)arg; //打印子线程ID printf("%dth child thread id = %lu\n",num,pthread_self()); return NULL; } int main(int argc,const char *argv[]) { //创建一个子线程 //线程id变量 pthread_t pthid[5]; for(int i = 0;i < 5;++i) { //第四个参数传递的是地址 //pthread_create(&pthid[i],NULL,myfunc,(void*)&i); //传值 pthread_create(&pthid[i],NULL,myfunc,(void*)i); count = i; } printf("parent thread id = %lu\n",pthread_self()); for(int i = 0;i < 5;i++) { printf("%d\n",i); } sleep(2); return 0; }

    运行结果: 注意事项: 1.其中,警告部分在此程序中可忽略,因为它是由于我们在程序中进行强转,int和char类型不匹配造成的,但是在此程序中,子线程共用同一块地址,所以不用担心。 2.在程序中,pthread_create函数的第四个参数不能传地址,因为这些子线程共用的相同的地址空间,会导致输出的结果中出现多次相同的线程序号。 3.在输出中,由于线程之间争夺系统资源,所以输出的序号不是顺序的。

    单个线程退出

    void pthread_exit(void *retval); retval 必须指向全局,堆 该函数可使单个线程退出且不影响其他线程

    #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <string.h> #include <pthread.h> void* myfunc(void* arg) { //打印子线程ID printf("child thread id = %lu\n",pthread_self()); for(int i = 0;i < 5;++i) { printf("child i = %d\n",i); if(i == 2) { //子线程退出 pthread_exit(NULL);//或return NULL:也可 } } } int main(int argc,const char *argv[]) { //创建一个子线程 //线程id变量 pthread_t pthid; //返回错误号 int ret = pthread_create(&pthid,NULL,myfunc,NULL); if(ret != 0) { printf("error number:%d\n",ret); //打印错误信息 printf("%s\n",strerror(ret)); } printf("parent thread id = %lu\n",pthread_self()); for(int i = 0;i < 5;i++) { printf("%d\n",i); } sleep(2); return 0; }

    运行结果:

    回收子线程资源

    pthread_join – 阻塞等待线程退出,获取线程退出状态 int pthread_join(pthread_t thread,void **retval); 参数: thread:要回收的子线程的线程id retval:读取线程退出时携带的状态信息 ----传出参数 ----void *ptr; ----pthread_join(pthid,&ptr); ----指向的内存和pthread_exit参数指向同一块内存地址

    //使用pthread_join回收子线程资源 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <string.h> #include <pthread.h> int number = 100; void* myfunc(void* arg) { //打印子线程ID printf("child thread id = %lu\n",pthread_self()); for(int i = 0;i < 5;++i) { printf("child i = %d\n",i); if(i == 2) { //子线程退出 pthread_exit(&number);//或return NULL:也可 } } } int main(int argc,const char *argv[]) { //创建一个子线程 //线程id变量 pthread_t pthid; //返回错误号 int ret = pthread_create(&pthid,NULL,myfunc,NULL); if(ret != 0) { printf("error number:%d\n",ret); //打印错误信息 printf("%s\n",strerror(ret)); } printf("parent thread id = %lu\n",pthread_self()); //阻塞等待子线程退出,并回收pcb void *ptr = NULL; pthread_join(pthid,&ptr); printf("number = %d\n",*(int*)ptr); int i = 0; while(i < 10) { printf("parent i = %d\n",++i); } sleep(2); return 0; }

    运行结果:

    相关函数

    线程分离 int pthread_detach(pthread_t thread); 注:调用该函数后,不需要再调用pthread_join,子线程会自动回收自己的pcb

    杀死/取消线程 int pthread_cancel(pthread_t thread); 注:在要杀死的子线程对应的处理函数的内部,必须做过一次系统调用。(pthread_testcancel(),write(),read(),printf等都会进行系统调用)

    比较两个线程id是否相等(预留函数) int pthread_equal(pthread_t t1,pthread_t t2);

    线程属性

    在线程创建函数pthread_create中,第二个参数pthread_attr_t attr即为线程函数的属性,它的数据类型为pthread_attr_t。

    线程属性操作函数:对线程属性变量的初始化: int pthread_attr_init(pthread_attr_t* attr);

    设置线程分离属性: int pthread_attr_setdetachstate( pthread_attr_t* attr, int detachstate );

    参数: attr:线程属性 detachstate: PTHREAD_CREATE_DETACHED(分离) PTHREAD_CREATE_JOINABLE(非分离)

    释放线程资源函数: int pthread_attr_destroy(pthread_attr_t* attr);

    下面码一个设置分离属性的例子:

    //pthread_attr.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <string.h> #include <pthread.h> void* myfunc(void* arg) { //打印子线程ID printf("child thread id = %lu\n",pthread_self()); return NULL; } int main(int argc,const char *argv[]) { //创建一个子线程 //线程id变量 pthread_t pthid; //打印错误号 //初始化线程的属性 pthread_attr_t attr; pthread_attr_init(&attr); //设置分离 pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //创建线程的时候设置线程分离 int ret = pthread_create(&pthid,NULL,myfunc,NULL); if(ret != 0) { printf("error number = %d\n",ret); //打印错误信息 printf("%s\n",strerror(ret)); } printf("parent thread id = %lu\n",pthread_self()); for(int i = 0;i < 5;i++) { printf("%d\n",i); } sleep(2); //释放资源 pthread_attr_destroy(&attr); return 0; }

    运行结果:

    线程同步

    cpu在计数时:

    寄存器只存储少量数据实时与物理内存做数据交换 取值时:通过内存地址

    来看一段代码:

    #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <pthread.h> #define MAX 10000 int number; void* funcA_num(void* arg) { for(int i = 0;i < MAX;++i) { int cur = number; cur++; number = cur; printf("Thread A, id = %lu,number = %d\n",pthread_self(),number); usleep(10); } return NULL; } void* funcB_num(void* arg) { int cur = number; cur++; number = cur; printf("Thread B, id = %lu,number = %d\n",pthread_self(),number); usleep(10); } int main(int argc,const char* argv[]) { pthread_t p1,p2; //创建两个子线程 pthread_create(&p1,NULL,funcA_num,NULL); pthread_create(&p2,NULL,funcB_num,NULL); //阻塞,资源回收 pthread_join(p1,NULL); pthread_join(p2,NULL); return 0; }

    运行结果: 输出结果中,就存在这种被覆盖的数据混乱现象。以下是出现这种现象的原因:

    操作了共享资源cpu调度问题 解决方法:线程同步,即按照先后顺序执行操作

    线程同步的思想: 通过给共享资源的源代码加锁以保证线程同步及获取资源的正确性 例:如果线程1想要访问共享资源,那么它需要先判断共享资源是否为锁状态,如果锁着,线程1阻塞在这把锁上;如果锁打开着,线程1访问共享资源,并上锁。当线程1访问共享资源结束后,将锁解开。 本来多线程可以并行访问共享资源,通过加锁机制,实现并行到串行。

    最新回复(0)