• Linux高级编程——线程


    pthread 线程
       

        概念 :线程是轻量级进程,一般是一个进程中的多个任务
                    进程是系统中最小的资源分配单位.
                    线程是系统中最小的执行单位。

      优点:多进程节省资源,可以共享变量

    进程会占用3g左右的空间,线程只会占用一部分,大概8M的空间

    进程的父子不会共享,但一个进程之间的线程的资源可以共享.

    进程的父子不是平级关系,线程是平级关系

     特征:s's
        1、共享资源
        2、效率高  30%
        3、三方库: pthread  clone   posix
                3.1 编写代码头文件: pthread.h
                3.2 编译代码加载库: -lpthread   library 
                libpthread.so  (linux库)
                gcc 1.c -lpthread     -lc
        缺点:
        1,线程和进程相比,稳定性,稍微差些
        2,线程的调试gdb,相对麻烦些。
            info thread 
            *1  
            2 
            3
            thread 3 
            
    线程与进程区别:

        资源:
            线程比进程多了共享资源。  IPC
            线程又具有部分私有资源。
            进程间只有私有资源没有共享资源。
        空间:
            进程空间独立,不能直接通信。
            线程可以共享空间,可以直接通信。

           进程解决相对复杂的问题,线 程解决相对复杂的问题.

    共同点:

    二者都可以并发

    3、线程的设计框架  posix
        

    创建多线程 ==》线程空间操作 ===》线程资源回收
    errno   strerror(errno)  perror();
      

     3.1 创建多线程:


        int pthread_create(
            pthread_t *thread const pthread_attr_t *attr,
            void *(*start_routine) (void *), void *arg);
        功能:该函数可以创建指定的一个线程。
        参数:thread 线程id,需要实现定义并由该函数返回。
              attr   线程属性,一般是NULL,表示默认属性。
              start_routine      指向指针函数的函数指针。
                      本质上是一个函数的名称即可。称为
    th                回调函数,是线程的执行空间。
    {
    }
              arg  回调函数的参数,即参数3的指针函数参数。
     

     返回值:成功 0
                    失败 错误码

    注意:一次pthread_create执行只能创建一个线程。
          每个进程至少有一个线程称为主线程。
          主线程退出则所有创建的子线程都退出。暂时先用while(1); 
          主线程必须有子线程同时运行才算多线程程序。
          线程id是线程的唯一标识,是CPU维护的一组数字。
          pstree 查看系统中多线程的对应关系。
          多个子线程可以执行同一回调函数。
        ps -eLf 查看线程相关信息Low Weigth Process
        ps -eLo pid,ppid,lwp,stat,comm

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void *th1(void*arg)
    7. {
    8. while(1)
    9. {
    10. printf("发送视频\n");
    11. sleep(1);
    12. }
    13. }
    14. void *th2(void*arg)
    15. {
    16. while(1)
    17. {
    18. printf("接受控制\n");
    19. }
    20. }
    21. int main(int argc, const char *argv[])
    22. {
    23. pthread_t tid1,tid2;
    24. pthread_create(&tid1,NULL,th1,NULL);
    25. pthread_create(&tid2,NULL,th2,NULL);
    26. while(1);
    27. return 0;
    28. }
    1. main 函数开始执行。
    2. 使用 pthread_create 创建了两个线程 tid1 和 tid2
    3. th1 线程开始执行其无限循环,并在每次迭代中打印 "发送视频",然后暂停一秒。
    4. 同时(几乎是同时),th2 线程也开始执行其无限循环,不断打印 "接受控制"。
    5. 因为两个线程是并发执行的,所以它们之间没有固定的打印顺序。这取决于操作系统调度器的决策,哪个线程在何时获得CPU时间片。
    6. main 函数中的 while(1); 是一个空循环,它使主线程保持活动状态,防止程序立即退出。然而,这个空循环并没有为程序提供任何有用的功能,通常你可能会使用某种形式的线程同步或等待(如 pthread_join)来确保主线程在所有其他线程完成后才退出。

    此时输出是乱的,是由于

    • 线程调度是由操作系统控制的,它决定哪个线程在何时运行。这取决于许多因素,包括线程优先级、系统负载、可用的CPU核心数量等。
    • 由于两个线程都在无限循环中,并且没有同步机制(如互斥锁、条件变量等),所以它们会尽可能快地交替执行(或并行执行,如果系统有多个CPU核心),导致输出看起来没有规律。

    2、pthread_t pthread_self(void); unsigned long int; %lu  获取线程号


       功能:获取当前线程的线程id
       参数:无
       返回值:成功 返回当前线程的线程id
                   失败  -1;
                syscall(SYS_gettid);
    这个方法重启后失效
    alias gcc='gcc -g -pthread '
    unalias gcc 

    永久起作用
    cd ~ //家目录
    vim .bashrc
    alias gcc='gcc -g -pthread '  :wq

    source .bashrc  生效

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void *th1 (void*arg)
    7. {
    8. while(1)
    9. {
    10. printf("发送视频 %lu\n",pthread_self());
    11. sleep(1);
    12. }
    13. }
    14. void *th2 (void*arg)
    15. {
    16. while(1)
    17. {
    18. printf("接受控制 %lu\n",pthread_self());
    19. sleep(1);
    20. }
    21. }
    22. int main(int argc, char *argv[])
    23. {
    24. pthread_t tid1,tid2;
    25. pthread_create(&tid1,NULL,th1,NULL);
    26. pthread_create(&tid2,NULL,th2,NULL);
    27. printf("main th %lu\n",pthread_self());
    28. while(1);
    29. return 0;
    30. }
    • 使用pthread_create创建两个线程:tid1(运行th1)和tid2(运行th2)。
    • 打印主线程的ID。
    • 使用while(1);使主线程进入无限循环,以保持程序运行。否则,当主线程结束时,程序可能会立即终止,导致其他线程也被终止

    练习题:
        设计一个多线程程序,至少有三个子线程
        每个线程执行不同的任务,并实时打印执行
        过程,同时表明身份。

        eg: ./a.out  ==>tid =xxx...  zheng ...
                        tid2 = xxx wozai.
                        tid3 = xxx  wozai ssss


    线程的退出:

    1.直接用return;   

    2: 自行退出 ==》自杀  ==》子线程自己退出
            exit(1);
            void pthread_exit(void *retval);  exit  return p;
            功能:子线程自行退出
            参数: retval 线程退出时候的返回状态,临死遗言。
            返回值:无

                th
                {
                    int a =10;

                    pthread_exit(&a);
                }
                join(,&ret)

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void *th1 (void*arg)
    7. {
    8. int i =10;
    9. while(i--)
    10. {
    11. printf("发送视频 %lu\n",pthread_self());
    12. sleep(1);
    13. }
    14. pthread_exit(NULL);//return NULL;
    15. }
    16. void *th2 (void*arg)
    17. {
    18. int i = 10;
    19. while(i--)
    20. {
    21. printf("接受控制 %lu\n",pthread_self());
    22. sleep(1);
    23. }
    24. pthread_exit(NULL);
    25. }
    26. int main(int argc, char *argv[])
    27. {
    28. pthread_t tid1,tid2;
    29. pthread_create(&tid1,NULL,th1,NULL);
    30. pthread_create(&tid2,NULL,th2,NULL);
    31. printf("main th %lu\n",pthread_self());
    32. while(1);
    33. return 0;
    34. }


        3. 强制退出 ==》他杀  ==》主线程结束子线程
            int pthread_cancel(pthread_t thread);
            功能:请求结束一个线程  (在主线程种调用 写入某个线程id号,可以关闭该线程)
            参数:thread 请求结束一个线程tid(想要关闭的线程id号)
            返回值:成功 0
                    失败 -1;

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void *th1 (void*arg)
    7. {
    8. while(1)
    9. {
    10. printf("发送视频\n");
    11. sleep(1);
    12. }
    13. }
    14. void *th2 (void*arg)
    15. {
    16. while(1)
    17. {
    18. printf("接受控制\n");
    19. sleep(1);
    20. }
    21. }
    22. int main(int argc, char *argv[])
    23. {
    24. pthread_t tid1,tid2;
    25. pthread_create(&tid1,NULL,th1,NULL);
    26. pthread_create(&tid2,NULL,th2,NULL);
    27. int i = 0 ;
    28. while(1)
    29. {
    30. i++;
    31. if(3 == i )
    32. {
    33. pthread_cancel(tid1);
    34. }
    35. if(5 ==i)
    36. {
    37. pthread_cancel(tid2);
    38. }
    39. sleep(1);
    40. }
    41. return 0;
    42. }

    作业:
        创建一个多线程程序,至少有10个子线程,
        每个线程有会打印不同的数据,同时表明身份。

        
        线程的回收


        1、线程的回收机制 ====》不同与进程没有孤儿线程和僵尸线程。
                                        ====》主线程结束任意生成的子线程都会结束。
                                          ====》 子线程的结束不会影响主线程的运行。
        char * retval ; retval++; 1 
        int * retval; 

        int pthread_join(pthread_t thread, void **retval);    
      功能:通过该函数可以将指定的线程资源回收,该函数具有阻塞等待功能,如果指定的线程没有结束,则回收线程会阻塞。
      参数:thread  要回收的子线程tid
                  retval  要回收的子线程返回值/状态。==》ptread_exit(值);
      返回值:成功 0
                     失败 返回一个错误号,是一个大于零的数;

                      失败可以用

                   

    1. #include
    2. #include
    3. #include
    4. void *th1 (void*arg)
    5. {
    6. int i = 10;
    7. while(i--)
    8. {
    9. printf("发送视频\n");
    10. sleep(1);
    11. }
    12. }
    13. void *th2 (void*arg)
    14. {
    15. int i = 10;
    16. while(i--)
    17. {
    18. printf("接受控制\n");
    19. sleep(1);
    20. }
    21. }
    22. int main(int argc, char *argv[])
    23. {
    24. pthread_t tid1,tid2;
    25. pthread_create(&tid1,NULL,th1,NULL);
    26. int ret = pthread_create(&tid2,NULL,th2,NULL);
    27. if(ret!=0)
    28. {
    29. // perror()
    30. fprintf(stderr,"error %s\n",strerror(ret));//exit();
    31. }
    32. pthread_join(tid1,NULL);
    33. pthread_join(tid2,NULL);
    34. return 0;
    35. }

    子线程的回收策略:
      1、如果预估子线程可以有限范围内结束则正常用pthread_join等待回收。
      2、如果预估子线程可能休眠或者阻塞则等待一定时间后强制回收。
      3、如果子线程已知必须长时间运行则,不再回收其资源。
      
      


      线程的参数,返回值
      

    1、传参数
            
        传整数 ===》int add(int a,int b);  ///a b 形参
                    add(x,y);    x y 实参 

            pthread_create(&tid,NULL,fun,x);

            fun ==>void * fun(void * arg);
            
        练习:创建一个子线程并向该线程中传入一个字符在
              线程中打印输出。
              在此基础上向子线程中传入一个字符串,并在
              子线程中打印输出。

              
              add(int a, int b)
              {
                int c = a+b;
                char buf[]=""
                return c;
              }
                        5
              int d = add(2,3);

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void* th(void* arg)
    7. {
    8. static int a =20;
    9. return &a;
    10. }
    11. int main(int argc, char *argv[])
    12. {
    13. pthread_t tid;
    14. void* ret;
    15. pthread_create(&tid,NULL,th,NULL);
    16. pthread_join(tid,&ret);
    17. printf("ret %d\n",*(int*)ret);
    18. return 0;
    19. }


        传字符串
            栈区字符数组:
            字符串常量:
            char *p = "hello";
            堆区字符串;
                char *pc = (char *)malloc(128);
                ptread_create(&tid,NULL,fun,pc);

                pthread_join(tid,NULL);

                free(pc);
            
                fun(void *arg)
                {
                    char * pc = (char *)arg    ;
                    printf("%s \n",pc);
                            %c
                }

    栈区

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void* th(void* arg)
    7. {
    8. static char buf[256]={0};
    9. strcpy(buf,"要消亡了\n");
    10. return buf;
    11. }
    12. int main(int argc, char *argv[])
    13. {
    14. pthread_t tid;
    15. void* ret;
    16. pthread_create(&tid,NULL,th,NULL);
    17. pthread_join(tid,&ret);
    18. printf("ret %s\n",(char*)ret);
    19. return 0;
    20. }

    堆区:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void* th(void* arg)
    7. {
    8. char * tmp = (char* )arg;
    9. strcpy(tmp,"hello");
    10. return tmp;
    11. }
    12. int main(int argc, char *argv[])
    13. {
    14. pthread_t tid;
    15. char * p = (char*)malloc(50);
    16. void* ret;
    17. pthread_create(&tid,NULL,th,p);
    18. pthread_join(tid,&ret);
    19. printf("ret %s\n",(char*)ret);
    20. free(p);
    21. return 0;
    22. }

      

     传结构体
        1、定义结构体类型
        2、用结构体定义变量
        3、向pthread_create传结构体变量
        4、从fun子线程中获取结构体数据

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. typedef struct
    7. {
    8. char * p;
    9. int a;
    10. }TH_ARG;
    11. void* th(void* arg)
    12. {
    13. TH_ARG * tmp = (TH_ARG* )arg;
    14. strcpy(tmp->p,"hello");
    15. //strcpy( ((TH_ARG*)arg)->p ,"hello");
    16. tmp->a +=10;
    17. return tmp;
    18. }
    19. int main(int argc, char *argv[])
    20. {
    21. pthread_t tid;
    22. int a =20;
    23. char * p = (char*)malloc(50);
    24. TH_ARG arg;
    25. arg.a = a;
    26. arg.p = p;
    27. void* ret;
    28. pthread_create(&tid,NULL,th,&arg);
    29. pthread_join(tid,&ret);
    30. printf("ret %s %d\n",((TH_ARG*)ret)->p,((TH_ARG*)ret)->a);
    31. free(p);
    32. return 0;
    33. }

    练习:
        定义一个包含不同数据类型的测试结构体
        并向子线程传参数,同时在子线程中打印输出。


        定义一个回调函数可以完成计算器的功能
        定义一个数据结构体可以一次传入不同的数据
        和计算方式并将结果打印输出。
        //2 + 3.6 
        // 2 + 3  2+3
        // 8 * 6
        typedef strcut
        {
            float a;
            float b;
            char c;//+ - * / 
            float d;
        }JSQ;
        
        

    返回值:pthread_exit(0) ===>pthread_exit(9);
            pthread_join(tid,NULL); ===>pthread_join(tid,?);
    10;
    -10;
    int * p =malloc(4);
    *p = -10;
    1、pthread_exit(?) ==>? = void * retval;
                              纯地址

    2、pthread_join(tid,?) ==>? = void **retval;
                                地址的地址
    原理:子线程退出的时候,可以返回一个内存地址
          改值所在的内存中可以存储任何数据,只要
          地址存在,则数据都可以正常返回。
        
        地址有三种:
        0、栈区变量  错误,子线程结束该地址失效。
        1、全局变量  失去意义,本质可以直接访问。

        2、静态变量 
        3、堆区变量


          主线程通过一个地址形式的变量来接受子进程
          返回的地址变量就可以将该地址中的数据取到。

        练习:从子线程中申请一块堆区内存并存字符串
          将该字符串以返回值形式返回到主线程并打印输出。
              
     

       设置分离属性,目的线程消亡,自动回收空间。
      

    主线程没有空,才设置分离属性来回收.

     attribute

     int pthread_attr_init(pthread_attr_t *attr);
        功能,初始化一个attr的变量
        参数:attr,需要变量来接受初始值
        返回:0  成功,
        非0 错误;
           int pthread_attr_destroy(pthread_attr_t *attr);
          功能:销毁attr变量。
          attr,属性变量
          返回:0  成功,
        非0 错误;
           
           
        man -k 
         int pthread_attr_setdetachstate(pthread_attr_t *attr
    , int detachstate);

        功能:把一个线程设置成相应的属性
        参数,attr,属性变量,有init函数初始化他。
        detachstate:有2个可选值,
        
        PTHREAD_CREATE_DETACHED:设置分离属性。
        
        第二种设置分离属性:
    int pthread_deatch(pthread_t thread);
        功能,设置分离属性
        参数,线程id号,填自己的id
        
        do{
        
        
        }while()

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void* th(void* arg)
    7. {
    8. pthread_detach(pthread_self());
    9. return NULL;
    10. }
    11. int main(int argc, char *argv[])
    12. {
    13. pthread_t tid;
    14. int i = 0 ;
    15. for(i=0;i<50000;i++)
    16. {
    17. int ret = pthread_create(&tid,NULL,th,NULL);
    18. if(ret!=0)
    19. {
    20. break;
    21. }
    22. // pthread_detach(tid);
    23. }
    24. printf("%d \n",i);
    25. return 0;
    26. }


    void pthread_cleanup_push(void (*routine)(void *), void *arg);

        功能:注册一个线程清理函数
        参数,routine,线程清理函数的入口
            arg,清理函数的参数。
        返回值,无

     
    void pthread_cleanup_pop(int execute);

        功能:调用清理函数
        execute,非0  执行清理函数
                         0 ,不执行清理
                
        返回值,无

    这两个函数成对出现,清理函数是在程序执行完毕后,系统调用的函数。

    do
    {

    }while(1)

    如果上面两个函数不是成对出现,会出现do -while()函数错误。

    process                thread
    fork                pthread_create 
    getpid,ppid,        pthread_self
    exit,                pthread_exit 
    wait,waitpid,        pthread_join 
    kill,                pthread_cancel
    atexit                 pthread_clean,
    exec                system--->fork->exec (ls)

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void clean(void* arg)
    7. {
    8. printf("this is clean ,arg is %s\n",(char*)arg);
    9. free(arg);
    10. }
    11. void* th(void*arg)
    12. {
    13. pthread_cleanup_push(clean,arg);
    14. printf("th arg is %s\n",(char*)arg);
    15. pthread_cleanup_pop(1);
    16. return NULL;
    17. }
    18. int main(int argc, char *argv[])
    19. {
    20. pthread_t tid;
    21. char* p = (char*)malloc(50);
    22. strcpy(p,"hello");
    23. pthread_create(&tid,NULL,th,p);
    24. pthread_join(tid,NULL);
    25. return 0;
    26. }


                        
     

  • 相关阅读:
    使用 Azure OpenAI 打造自己的 ChatGPT
    vue 封装菜单组件 来回跳转使菜单高亮
    nfs 部署
    用PHP异步协程控制python爬虫脚本,实现多协程分布式爬取
    手写Promise.all/race/any/settled方法
    Yolov5 v7.0目标检测——详细记录环境配置、自定义数据处理、模型训练与常用错误解决方法(数据集为河道漂浮物)
    SerializationException: Could not read JSON: Could not resolve type
    自学黑客【网络安全】,一般人我劝你还是算了吧
    npm install 时候 停留在某个git 资源依赖上
    SSM框架-MyBatis核心配置文件详解与项目补充
  • 原文地址:https://blog.csdn.net/m0_71703182/article/details/140036301