• Linux下的系统编程——守护进程、线程(十二)


    前言:

    我们知道进程拥有一个PCB,在Linux中被称为task_struct,并且有一个进程地址空间,也有一个页表,通过页表指向物理内存,但是从今天开始,对进程的概念可能发生变化,这个我们后边来说,在Linux中,并没有真正的线程,而是使用进程的PCB来模拟线程,也就是说一个线程在创建时,只会去创建一个PCB,而这个PCB也指向主线程的虚拟地址空间,和其他线程一起共享内存的代码和数据。一个线程也被称为一个执行流,这是因为线程是被CPU调度的执行流,而一个进程就是分配系统资源的基本实体。下面让我们进入线程的学习吧!

    目录

    一、进程组和会话

    *二、守护进程:

    1.守护进程:

    2.守护进程创建步骤:

    ​编辑 三、线程

    1.三级映射:

    2.线程概念:

    3.线程共享:

    (1)线程共享资源

    (2) 线程非共享资源

    4.线程优、缺点

    5.线程控制原语:

    1)获取线程id

    2) 创建子线程。

     3)循环创建N个子线程:

    4)线程中全局变量共享

    5)   退出当前线程

    6)   阻塞 回收线程

       7)杀死一个线程。

    8)设置线程分离

    9)   线程与进程原语比较

    6.线程属性:

        


    一、进程组和会话

            进程组,也称之为作业。BSD 于 1980 年前后向 Unix 中增加的一个新特性。代表一个或多个进程的集合。每个 进程都属于一个进程组。在 waitpid 函数和 kill 函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简 化对多个进程的管理。

            当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组 ID==第一个进程 ID(组长进程)。 所以,组长进程标识:其进程组 ID==其进程 ID

    可以使用 kill -SIGKILL -进程组 ID(负的)来将整个进程组内的进程全部杀死。                                 

            组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。

    *二、守护进程:

    1.守护进程:

            daemon(精灵)进程。通常运行与操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作

            不受用户登录注销影响。通常采用以d结尾的命名方式

    **2.守护进程创建步骤:

        1. fork子进程,让父进程终止

        2. 子进程调用 setsid() 创建新会话

        3. 通常根据需要,改变工作目录位置 chdir(), 防止目录被卸载。

        4. 通常根据需要,重设umask文件权限掩码,影响新文件的创建权限。  

            022 -- 755    0345 --- 432   r---wx-w-   422

        5. 通常根据需要,关闭/重定向文件描述符

        6. 守护进程业务逻辑。while()

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. void sys_err(const char *str){
    10. perror(str);
    11. exit(1);
    12. }
    13. int main(int argc,char *argv[])
    14. {
    15. pid_t pid;
    16. int ret,fd;
    17. pid = fork(); //创建进程
    18. if(pid > 0) //父进程终止
    19. exit(0);
    20. pid = setsid(); //创建新会话
    21. if(pid == -1)
    22. sys_err("setsid error");
    23. ret = chdir("/home/book/Desktop"); //改变工作目录位置
    24. if(ret == -1)
    25. sys_err("chdir error");
    26. umask(0022); //改变文件访问权限掩码
    27. close(STDIN_FILENO); //关闭文件描述符 0
    28. fd = open("/dev/null",O_RDWR); //fd --> 0
    29. if(fd == -1)
    30. sys_err("open error");
    31. dup2(fd,STDOUT_FILENO); //重定向stdout
    32. dup2(fd,STDERR_FILENO); //重定向stderr
    33. while(1); //模拟守护进程业务
    34. return 0;
    35. }

    用户的登录注销不影响进程

     三、线程

    1.三级映射:

    三级映射:

            进程 PCB --> 页目录(可看成数组,首地址位于 PCB 中) --> 页表 --> 物理页面 --> 内存单元

    2.线程概念:

        进程:有独立的进程地址空间。有独立的pcb。        分配资源的最小单位。

        线程:有独立的pcb。没有独立的进程地址空间。    最小单位的执行。

        ps -Lf 进程id     ---> 线程号。        LWP  ---> cpu 执行的最小单位。

     最小执行单位:

    3.线程共享:

            独享栈空间内核栈、用户栈

            共享 ./text./data ./rodataa ./bsss heap  ---> 共享【全局变量】(errno)

    (1)线程共享资源

                    1).文件描述符表

                    2).每种信号的处理方式

                    3).当前工作目录

                    4).用户 ID 和组 ID

                    5).内存地址空间 (.text/.data/.bss/heap/共享库)

    (2) 线程非共享资源

                    1).线程 id

                    2).处理器现场和栈指针(内核栈)

                    3).独立的栈空间(用户空间栈)

                    4).errno 变量

                    5).信号屏蔽字

                    6).调度优先级

    4.线程优、缺点

            优点: 1. 提高程序并发性   2. 开销小   3. 数据通信、共享数据方便

            缺点: 1. 库函数,不稳定   2. 调试、编写困难、gdb 不支持  3. 对信号支持不好

            优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、线程差别不是很大。

    5.线程控制原语:

    1)获取线程id

    线程id是在进程地址空间内部,用来标识线程身份的id号。

    pthread_t pthread_self(void);            

            返回值:本线程id

    2) 创建子线程。

    int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*start_rountn)(void *), void *arg);    

            参1:传出参数,表新创建的子线程 id

            参2:线程属性。传NULL表使用默认属性

            参3:子线程回调函数。创建成功,pthread_create函数返回时,该函数会被自动调用。
            
            参4:参3的参数。没有的话,传NULL

            返回值:成功:0

                失败:errno

    获取线程id、创建子线程:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void sys_err(const char *str)
    8. {
    9. perror(str);
    10. exit(1);
    11. }
    12. //子线程
    13. void *tfn(void *arg) //子进程回调函数
    14. {
    15. printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());//获取进程id
    16. return NULL;
    17. }
    18. //主线程
    19. int main(int argc, char *argv[])
    20. {
    21. pthread_t tid;
    22. int ret = pthread_create(&tid, NULL, tfn, NULL); //创建子进程
    23. if (ret != 0) {
    24. perror("pthread_create error");
    25. }
    26. printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());//获取进程id
    27. //sleep(1);
    28. //return 0;
    29. pthread_exit((void *)0);
    30. }

     *3)循环创建N个子线程:

            for (i = 0; i < 5; i++)

                pthread_create(&tid, NULL, tfn, (void *)i);   // 将 int 类型 i, 强转成 void *, 传参。    
     

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void sys_err(const char *str)
    8. {
    9. perror(str);
    10. exit(1);
    11. }
    12. void *tfn(void *arg)
    13. {
    14. int i = (int)arg; //强转.受参数类型限制
    15. sleep(i);
    16. printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
    17. return NULL;
    18. }
    19. int main(int argc, char *argv[])
    20. {
    21. int i;
    22. int ret;
    23. pthread_t tid;
    24. for (i = 0; i < 5; i++) {
    25. ret = pthread_create(&tid, NULL, tfn, (void *)i); // i 传参采用 值传递. 借助强转.
    26. if (ret != 0) {
    27. sys_err("pthread_create error");
    28. }
    29. }
    30. sleep(i);
    31. printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self());
    32. return 0;
    33. }

    4)线程中全局变量共享

    线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量,只能借助 mmap

    1. #include
    2. #include
    3. #include
    4. #include
    5. int var = 100;
    6. void *tfn(void *arg)
    7. {
    8. var = 200;
    9. printf("thread, var = %d\n", var);
    10. return NULL;
    11. }
    12. int main(void)
    13. {
    14. printf("At first var = %d\n", var);
    15. pthread_t tid;
    16. pthread_create(&tid, NULL, tfn, NULL);
    17. sleep(1);
    18. printf("after pthread_create, var = %d\n", var);
    19. return 0;
    20. }

     如果遇到以下编译问题,不要慌张

    问题的原因pthread不是linux下的默认的库,也就是在链接的时候,无法找到phread库中哥函数的入口地址,于是链接会失败。

    解决:在gcc编译的时候,附加要加 -lpthread参数即可解决。

    试用如下命令即可编译通过

     gcc z.c -o z -lpthread

    5)   退出当前线程

    void pthread_exit(void *retval);  

            retval:退出值。 无退出值时,NULL

            exit();    退出当前进程。

            return: 返回到调用者那里去。

            pthread_exit(): 退出当前线程。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void sys_err(const char *str)
    8. {
    9. perror(str);
    10. exit(1);
    11. }
    12. void *tfn(void *arg)
    13. {
    14. int i = (int)arg; //强转.受参数类型限制
    15. sleep(i);
    16. if(i == 2){
    17. }
    18. printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
    19. return NULL;
    20. }
    21. int main(int argc, char *argv[])
    22. {
    23. int i;
    24. int ret;
    25. pthread_t tid;
    26. for (i = 0; i < 5; i++) {
    27. ret = pthread_create(&tid, NULL, tfn, (void *)i); // i 传参采用 值传递. 借助强转.
    28. if (ret != 0) {
    29. sys_err("pthread_create error");
    30. }
    31. }
    32. sleep(i);
    33. printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self());
    34. return 0;
    35. }

     exit(0)   退出线程

    1. /********使用exit(0)**************/
    2. void *tfn(void *arg)
    3. {
    4. int i = (int)arg;
    5. sleep(i);
    6. if(i == 2){
    7. exit(0); //表示退出线程
    8. }
    9. printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
    10. return NULL;
    11. }

     return NULL 返回到调用者那里

    1. /**********使用return NULL**************/
    2. void *tfn(void *arg)
    3. {
    4. int i = (int)arg;
    5. sleep(i);
    6. if(i == 2){
    7. return NULL; //返回到调用者那里
    8. }
    9. printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
    10. return NULL;
    11. }

    pthread_exit(NULL)  当前线程退出

    1. /*****使用pthread_exit(NULL)  当前线程退出********/
    2. void func(void)
    3. {
    4. pthread_exit(NULL);
    5. return;
    6. }
    7. void *tfn(void *arg)
    8. {
    9. int i = (int)arg; //强转.受参数类型限制
    10. sleep(i);
    11. if(i == 2){
    12. func(); //当前线程退出
    13. }
    14. printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
    15. return NULL;
    16. }
    17. /*********或者*************/
    18. void *tfn(void *arg)
    19. {
    20. int i = (int)arg; //强转.受参数类型限制
    21. sleep(i);
    22. if(i == 2){
    23. pthread_exit(NULL); //当前线程退出
    24. }
    25. printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self());
    26. return NULL;
    27. }

    *6)   阻塞 回收线程

     int pthread_join(pthread_t thread, void **retval);    

            thread: 待回收的线程id

            retval:传出参数。 回收的那个线程的退出值。

                线程异常借助,值为 -1。

            返回值:成功:0

                失败:errno

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. struct thrd{
    8. int var;
    9. char str[256];
    10. };
    11. void sys_err(const char *str)
    12. {
    13. perror(str);
    14. exit(1);
    15. }
    16. void *tfn(void *arg)
    17. {
    18. struct thrd *tval;
    19. tval = malloc(sizeof(tval));
    20. tval->var = 100;
    21. strcpy(tval->str,"hello thread");
    22. return (void *)tval;
    23. }
    24. int main(int argc,char *argv[])
    25. {
    26. pthread_t tid;
    27. struct thrd *retval;
    28. int ret = pthread_create(&tid,NULL,tfn,NULL);
    29. if(ret != 0)
    30. sys_err("pthread_create error");
    31. ret = pthread_join(tid,(void **)&retval);
    32. if(ret != 0)
    33. sys_err("pthread_join error");
    34. printf("child thread exit with var = %d,str = %s\n",retval->var,retval->str);
    35. pthread_exit(NULL);
    36. return 0;
    37. }

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void sys_err(const char *str)
    8. {
    9. perror(str);
    10. exit(1);
    11. }
    12. void *tfn(void *arg)
    13. {
    14. return (void *)74;
    15. }
    16. int main(int argc,char *argv[])
    17. {
    18. pthread_t tid;
    19. int *retval;
    20. int ret = pthread_create(&tid,NULL,tfn,NULL);
    21. if(ret != 0)
    22. sys_err("pthread_create error");
    23. ret = pthread_join(tid,(void **)&retval);
    24. if(ret != 0)
    25. sys_err("pthread_join error");
    26. printf("child thread exit with %d\n",(void *)retval);
    27. pthread_exit(NULL);
    28. return 0;
    29. }

     注意:

     局部变量地址,不可做返回值

    1. void *tfn(void *arg)
    2. {
    3. struct thrd tval; //局部变量地址,不可做返回值
    4. tval = malloc(sizeof(tval));
    5. tval->var = 100;
    6. strcpy(tval->str,"hello thread");
    7. return (void *)tval;
    8. }

       7)杀死一个线程。

     需要到达取消点(保存点)

    int pthread_cancel(pthread_t thread);      

            thread: 待杀死的线程id
            
            返回值:成功:0

                失败:errno

            如果,子线程没有到达取消点, 那么 pthread_cancel 无效

            我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();

            成功被 pthread_cancel() 杀死的线程,返回 -1.使用pthead_join 回收

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void tfn()
    8. {
    9. while(1){
    10. printf("thread: pid = %d,tid = %lu\n",getpid(),pthread_self());
    11. sleep(1);
    12. }
    13. return NULL;
    14. }
    15. int main(int argc,char *argv[])
    16. {
    17. pthread_t tid;
    18. int ret = pthread_create(&tid,NULL,tfn,NULL);
    19. if(ret != 0){
    20. fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
    21. exit(1);
    22. }
    23. printf("main: pid =%d,tid = %lu\n",getpid,pthread_self());
    24. sleep(5);
    25. ret = pthread_cancel(tid);
    26. if(ret != 0){
    27. fprintf(stderr,"pthread_cancel error:%s\n",strerror(ret));
    28. exit(1);
    29. }
    30. while(1);
    31. return 0;
    32. }

    8)设置线程分离

    int pthread_detach(pthread_t thread);        

            thread: 待分离的线程id
            

                返回值:成功:0

                失败:errno    

    注意:检查出错返回:  线程中:

            fprintf(stderr, "xxx error: %s\n", strerror(ret));

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void *tfn(void *arg)
    8. {
    9. printf("thread: pid = %d, tid= %lu\n",getpid(), pthread_self());
    10. return NULL;
    11. }
    12. int main(int argc, char *argv[])
    13. {
    14. int ret;
    15. pthread_t tid;
    16. ret = pthread_create(&tid, NULL, tfn, NULL); // i 传参采用 值传递. 借助强转.
    17. if (ret != 0) {
    18. fprintf(stderr,"pthread_create error :%s\n",strerror(ret));
    19. exit(1);
    20. }
    21. ret = pthread_detach(tid); //设置线程分离,线程终止,会自动清理pcb,无需回收
    22. if (ret != 0) {
    23. fprintf(stderr,"pthread_detach error :%s\n",strerror(ret));
    24. exit(1);
    25. }
    26. sleep(2);
    27. ret = pthread_join(tid,NULL); //tid为无效值
    28. if (ret != 0) {
    29. fprintf(stderr,"pthread_join error :%s\n",strerror(ret));
    30. exit(1);
    31. }
    32. printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self());
    33. pthread_exit((void *)0);
    34. }


      

    9)   线程与进程原语比较

        线程控制原语                    进程控制原语


        pthread_create()                fork();

        pthread_self()                    getpid();

        pthread_exit()                    exit(); / return 

        pthread_join()                    wait()/waitpid()

        pthread_cancel()                kill()

        pthread_detach()

    6.线程属性:

        设置分离属性

        pthread_attr_t attr      创建一个线程属性结构体变量

        pthread_attr_init(&attr);    初始化线程属性

        pthread_attr_setdetachstate(&attr,  PTHREAD_CREATE_DETACHED); 

        设置线程属性为分离态

        pthread_create(&tid, &attr, tfn, NULL); 借助修改后的 设置线程属性 创建为分离态的新线程

        pthread_attr_destroy(&attr);    销毁线程属性

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void *tfn(void *arg)
    8. {
    9. printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
    10. return NULL;
    11. }
    12. int main(int argc, char *argv[])
    13. {
    14. pthread_t tid;
    15. pthread_attr_t attr;
    16. int ret = pthread_attr_init(&attr);
    17. if (ret != 0) {
    18. fprintf(stderr, "attr_init error:%s\n", strerror(ret));
    19. exit(1);
    20. }
    21. ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程属性为 分离属性
    22. if (ret != 0) {
    23. fprintf(stderr, "attr_setdetachstate error:%s\n", strerror(ret));
    24. exit(1);
    25. }
    26. ret = pthread_create(&tid, &attr, tfn, NULL);
    27. if (ret != 0) {
    28. perror("pthread_create error");
    29. }
    30. ret = pthread_attr_destroy(&attr);
    31. if (ret != 0) {
    32. fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));
    33. exit(1);
    34. }
    35. //sleep(1) //等待子线程死亡
    36. //因为join是可以阻塞的,所以不sleep也可以
    37. ret = pthread_join(tid, NULL); //回收子线程,回收失败线程分离
    38. if (ret != 0) {
    39. fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
    40. exit(1);
    41. }
    42. printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
    43. pthread_exit((void *)0);
    44. }

     7.线程使用注意事项

            1). 主线程退出其他线程不退出,主线程应调用 pthread_exit

            2). 避免僵尸线程

                     pthread_join

                     pthread_detach

                     pthread_create 指定分离属性

                    被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;

            3). malloc 和 mmap 申请的内存可以被其他线程释放

            4). 应避免在多线程模型中调用 fork 除非,马上 exec,子进程中只有调用 fork 的线程存在,其他线程在子进程 中均 pthread_exit

             5) . 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制


        


        

        


     

        

  • 相关阅读:
    猿创征文|瑞吉外卖——移动端_笔记
    Vue的SetUp函数
    产品解读 | 数据服务平台:KDP
    ceph 007 双向池同步 rgw对象网关配置 s3对象存储
    浅谈归并排序:合并 K 个升序链表的归并解法
    Vue实战项目1:跑马灯效果
    Three导入dae格式模型实例
    44、Flink之module模块介绍及使用示例和Flink SQL使用hive内置函数及自定义函数详细示例--网上有些说法好像是错误的
    构建煤矿物联网大数据平台思路(1)
    【python】numpy创建特殊数组
  • 原文地址:https://blog.csdn.net/m0_63168877/article/details/132891181