• Linux下线程编程


    Linux下线程编程

    1.线程简介

      线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
      线程是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针PC,寄存器集合和堆栈组成。线程是进程的实体,是被系统独立调度和分配的基本单位。一个线程可以创建和撤销另一个线程,同一进程的多个线程之间可以并发执行。线程由就绪、阻塞、运行三种基本状态。每一个程序至少有一个线程,若程序只有一个线程,那就是程序本身。
      在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

    2.线程与进程区别

      进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。
      另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。
      与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
      通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。
      线程与进程的区别可以归纳为以下4点:
      1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
      2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信需要进程同步和互斥手段的辅助,以保证数据的一致性。
      3)调度和切换:线程上下文切换比进程上下文切换要快得多。
      4)在多线程OS中,进程不是一个可执行的实体。
      进程和线程运行状态:**在这里插入图片描述

    3.线程相关函数

     3.1创建线程pthread_create

      pthread_create是Unix操作系统(Unix、linux等)的创建线程的函数。
      注:编译时需要指定链接库 -lpthread
      函数原型:

    #include
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
    形参: thread — 指向线程标志符的指针类型为:pthread_t *
      attr — 设置线程属性,默认填NULL。类型为:const pthread_attr_t *
      void *(*start_routine) (void *) — 函数指针,现在运行函数的起始地址
      arg — 运行函数的参数。不需要填NULL ,类型为:void *
    返回值: 成功返回0;失败返回错误编号。
      线程创建成功后,attr参数用于指定线程属性,新创建的线程函数形参只有一个void *形参,若需要传入的参数不止一个,则可以把需要传入的参数保存到一个结构体中,通过结构体传入。

      示例:

    #include 
    #include 
    #include 
    void *start_routine_func(void *arg)
    {
        while (1)
        {
          printf("子线程运行中。。。\n");
          sleep(1);
        } 
    }
    int main()
    {
        int stat;
        pthread_t pth;//线程标志符
        pthread_create(&pth,NULL,start_routine_func,NULL);
        while(1)
        {
            printf("主线程运行中。。。\n");
            sleep(1);
        }
    }
    [xsw@xsw 系统编程]$ gcc pthread.c -l pthread
    [xsw@xsw 系统编程]$ ./a.out 
    主线程运行中。。。
    子线程运行中。。。
    主线程运行中。。。
    子线程运行中。。。
    子线程运行中。。。
    主线程运行中。。。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

     3.2 退出线程pthread_exit

      函数原型:

    void pthread_exit(void *retval);
    函数功能:
      终止调用它的线程并通过形参返回一个指向某个对象的指针
    形 参: void *retval — 线程需要返回的地址
    返回值:
      注:线程结束必须释放线程堆栈,也就是线程函数必须调用pthread_exit()结束,否则直到主进程函数退出才释放。

      示例:

    #include 
    #include 
    #include 
    void *start_routine_func(void *arg)
    {
        int cnt=0;
        while (1)
        {
          printf("子线程运行中cnt=%d。。。\n",cnt);
          sleep(1);
          cnt++;
          if(cnt>=3)break;
        } 
        pthread_exit(NULL);//退出线程,释放堆栈
        
    }
    int main()
    {
        int stat;
        pthread_t pth;//线程标志符
        /*创建子线线程*/
        if(pthread_create(&pth,NULL,start_routine_func,NULL)!=0)
        {
            printf("线程创建失败\n");
            return 0;
        }
        printf("子线程ID=%lu\n",pth);
        /*等待线程退出*/
        pthread_join(pth,NULL);
        printf("线程退出成功\r\n");
        return 0;
    }
    [xsw@xsw 系统编程]$ gcc pthread.c -lpthread
    [xsw@xsw 系统编程]$ ./a.out 
    子线程ID=3078433648
    子线程运行中cnt=0。。。
    子线程运行中cnt=1。。。
    子线程运行中cnt=2。。。
    线程退出成功
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

     3.3 等待线程结束pthread_join

      函数原型:

    int pthread_join(pthread_t thread, void **retval);
    函数功能:
      以阻塞方式等待thread指定线程结束,当函数返回值,被等待线程的资源被回收。若线程已经结束,则立即返回。并且thread指定的线程必须是joinable(结合属性)属性。
    形 参: thread — 线程标志符(线程ID)。线程唯一标志,类型为:pthread_t
      retval — 用户定义的指针,用来存储被等待线程返回的地址
    返回值: 成功返回0,失败返回错误编号。

      示例:

    #include 
    #include 
    #include 
    void *start_routine_func(void *arg)
    {
        int cnt=0;
        while (1)
        {
          printf("子线程运行中cnt=%d。。。\n",cnt);
          sleep(1);
          cnt++;
          if(cnt>=3)break;
        } 
        pthread_exit(NULL);//退出线程,释放堆栈
        
    }
    int main()
    {
        int stat;
        pthread_t pth;//线程标志符
        /*创建子线线程*/
        if(pthread_create(&pth,NULL,start_routine_func,NULL)!=0)
        {
            printf("线程创建失败\n");
            return 0;
        }
        printf("子线程ID=%lu\n",pth);
        /*等待线程退出*/
        pthread_join(pth,NULL);
        printf("线程退出成功\r\n");
        return 0;
    }
    [xsw@xsw 系统编程]$ gcc pthread.c -lpthread
    [xsw@xsw 系统编程]$ ./a.out 
    子线程ID=3078433648
    子线程运行中cnt=0。。。
    子线程运行中cnt=1。。。
    子线程运行中cnt=2。。。
    线程退出成功
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

     3.4 获取当前线程标志符pthread_self

      函数原型:

    pthread_t pthread_self(void);
    函数功能:
      获取线程自身ID。
    形 参:
    返回值: 返回当前线程标志符。pthread_t类型为unsigned long int,打印应%lu。

      示例:

    #include 
    #include 
    #include 
    void *start_routine_func(void *arg)
    {
        printf("子线程ID=%lu运行中。。。\n",pthread_self());
        pthread_exit(NULL);//退出线程,释放堆栈
    }
    int main()
    {
        int stat;
        int i=0;
        pthread_t pth;//线程标志符
        printf("主线程ID=%lu\n",pthread_self());
        /*创建5个子线线程*/
        for(i=0;i<5;i++)
        {
            if(pthread_create(&pth,NULL,start_routine_func,NULL)!=0)
            {
                printf("线程创建失败\n");
                return 0;
            }
            printf("子线程ID=%lu\n",pth);
        }
        /*等待线程退出*/
        pthread_join(pth,NULL);
        printf("线程退出成功\r\n");
        return 0;
    }
    [xsw@xsw 系统编程]$ gcc pthread.c -lpthread
    [xsw@xsw 系统编程]$ ./a.out 
    主线程ID=3078706880
    子线程ID=3078703984
    子线程ID=3068214128
    子线程ID=3057724272
    子线程ID=3047234416
    子线程ID=3036744560
    子线程ID=3068214128运行中。。。
    子线程ID=3078703984运行中。。。
    子线程ID=3057724272运行中。。。
    子线程ID=3047234416运行中。。。
    子线程ID=3036744560运行中。。。
    线程退出成功
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

     3.5 自动清理线程资源

      函数原型:

    //注册清理函数
    void pthread_cleanup_push(void (*routine)(void *),void *arg);
    //释放清理函数
    void pthread_cleanup_pop(int execute);
    函数功能:
      线程清除处理函数,用于程序异常退出的时候做善后的资源清理。自动释放资源。
      注:pthread_cleanup_push函数与pthread_cleanup_pop函数需要成对调用。
    形 参:
      void (*routine)(void *) — 处理程序函数入口
      void *arg — 传递给处理函数形参
      int execute — 执行的状态值,0 – 不调用清理函数;1 – 调用清理函数。
    返回值:
    导致调用清理函数条件:
      1.调用pthread_exit()函数
      2.Pthread_claenup_pop的形参为1
      注:return不会导致清理函数调用。

      示例:

    #include 
    #include 
    #include 
    /*线程清理函数*/
    void routine_Clinen(void *arg)
    {
        printf("arg=%d\n",*(int *)arg);
        free(arg);
        printf("释放空间完成\n");
    }
    /*子线程函数*/
    void *start_routine_func (void *arg)
    {
        printf("arg=%s,线程运行中...\n",arg);
        char *p=malloc(4);
        *p=100;
        //注册线程清理函数
        pthread_cleanup_push(routine_Clinen,p);
        pthread_exit("子线程返回数据测试!");//释放线程堆栈
       // return 0;//return终止不会触发线程清理函数
        //调用线程清理函数
        pthread_cleanup_pop(1);
    }
    int main() 
    {
        /*1.创建线程*/
        char buff[]="线程传入参数测试";
        pthread_t thread;
        if(pthread_create(&thread,NULL,start_routine_func,buff)!=0)
        {
            printf("线程创建失败\n");
            return 0;
        }
        printf("线程ID=%lu\n",pthread_self());
        char *p;
        pthread_join(thread,(void **)&p);//等待线程退出
        printf("子线程返回数据:%s\n",p);
        printf("主线程退出\n");
        return 0;
    }
    [xsw@xsw 系统编程]$ gcc pthread.c -lpthread
    [xsw@xsw 系统编程]$ ./a.out 
    arg=线程传入参数测试,线程运行中...
    线程ID=3078866624
    arg=100
    释放空间完成
    子线程返回数据:子线程返回数据测试!
    主线程退出
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

      注:子线程退出时,return退出不会触发线程清理函数

     3.6 线程取消函数pthread_cancel

    int pthread_cancel(pthread_t thread);
    函数功能:
      取消同一进程中的其他线程。
    形 参:
      pthread_t thread — 线程描述符
    返回值: 0 — 成功,其他值 — 失败

      示例:

    #include 
    #include 
    #include 
    void *start_routine_func(void *arg)
    {
        int data=*(int *)arg;
        while(1)
        {
            printf("data=%d\n",data);
            sleep(1);
            data++;
        }
    }
    int main()
    {
        int data=10;
        pthread_t pth_id;
        if(pthread_create(&pth_id,NULL,start_routine_func,&data)!=0)
        {
            printf("线程创建失败\n");
            return 0;
        }
        printf("子线程ID:%lu\n",pth_id);
        while(1)
        {
            sleep(1);
            printf("主线程运行中data=%d\n",data);
            data++;
            if(data==15)
            {
                pthread_cancel(pth_id);//取消子线程
            }
        }
    }
    [xsw@xsw 系统编程]$ gcc pthread.c -lpthread
    [xsw@xsw 系统编程]$ ./a.out 
    子线程ID:3079162736
    data=10
    主线程运行中data=10
    data=11
    主线程运行中data=11
    data=12
    主线程运行中data=12
    data=13
    主线程运行中data=13
    data=14
    主线程运行中data=14
    主线程运行中data=15
    主线程运行中data=16
    主线程运行中data=17
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

     3.7 线程分离属性pthread_detach

      创建一个线程默认的状态是joinable(结合属性),如果一个线程结束但没有调用pthread_join,则它的状态类似于进程中的zombie process(僵尸进程),即还有一部分资源没有被回收(退出状态码),所以创建线程时应该使用函数pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似进程中的wait、waitpid)。但是调用pthread_join(pthread_id)函数后,如果该线程没有运行结束,调用者会被阻塞,有些情况下我们并不希望如此。pthread_detach函数可以将该线程状态设置为detached(分离状态),则该线程运行结束后自动会释放所有资源。
      函数原型:

    int pthread_detach(pthread_t thread);
    形 参:
      pthread_t thread — 线程标志符
    返回值: 0 — 成功,其它值 – 失败

      示例:

    #include 
    #include 
    #include 
    void *start_routine_func(void *arg)
    {
        int data=*(int *)arg;
        while(1)
        {
            printf("data=%d\n",data);
            sleep(1);
            data++;
        }
    }
    int main()
    {
        int data=10;
        pthread_t pth_id;
        if(pthread_create(&pth_id,NULL,start_routine_func,&data)!=0)
        {
            printf("线程创建失败\n");
            return 0;
        }
        printf("子线程ID:%lu\n",pth_id);
        //设置分离属性
        pthread_detach(pth_id);
        //等待子线程退出
        pthread_join(pth_id,NULL);//未设置分离属性则会阻塞主线程
        while(1)
        {
            sleep(1);
            printf("主线程运行中...\n");
        }
        return 0;
    }
    [xsw@xsw 系统编程]$ gcc pthread.c -lpthread
    [xsw@xsw 系统编程]$ ./a.out 
    子线程ID:3078335344
    data=10
    
    主线程运行中...
    data=11
    主线程运行中...
    data=12
    主线程运行中...
    data=13
    主线程运行中...
    data=14
    主线程运行中...
    data=15
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

     3.8 设置线程栈空间

      查看线程堆栈空间:

    [wbyq@wbyq ~]$ ulimit -s
    8192
    
    • 1
    • 2

      8192单位是KB,也就是默认栈空间大小为8M
      通过命令ulimit -a查看线程栈空间详细信息

    [wbyq@wbyq ~]$ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 15407
    max locked memory       (kbytes, -l) 65536
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 15407
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

      通过命令ulimit -s <栈空间大小>

    [wbyq@wbyq ~]$ ulimit -s 10240
    [wbyq@wbyq ~]$ ulimit -s 
    10240
    
    • 1
    • 2
    • 3

      每个线程的栈空间都是独立的,如果堆栈空间溢出程序会出现段错误。如果一个进程有10个线程,那么分配的栈空间大小为10*<每个线程栈空间大小>
      示例:

    #include 
    int main()
    {
        char buff[12*1024*1024+1]="hello,world\n";
        printf("buff=%s,%d",buff,sizeof(buff));
        return 0;
    }
    [xsw@xsw 系统编程]$ ./a.out 
    段错误 (core dumped)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

     3.9 通过函数设置和查询线程栈空间

    #include 
    #include 
    #include 
    int main()
    {
        /*查看线程栈空间最小值*/
        printf("STACK_MIN:%d\n",PTHREAD_STACK_MIN);//16384byte--16kb
        pthread_attr_t attr;
        size_t ret,stack_size;
        ret=pthread_attr_init(&attr);//初始化线程属性
        if(ret!=0)
        {
            printf("初始化失败\n");
            return 0;
        }
        /*获取线程栈空间*/
        ret=pthread_attr_getstacksize(&attr,&stack_size);
        printf("线程栈空间:%ld kb\n",stack_size/1024);
        /*设置线程栈空间*/
        stack_size=8*1024*1024;//8M
        pthread_attr_setstacksize(&attr,stack_size);
        /*获取线程栈空间*/
        ret=pthread_attr_getstacksize(&attr,&stack_size);
        printf("修改后栈空间:%ld kb\n",stack_size/1024);
    }
    [wbyq@wbyq ubuntu]$ gcc main.c -pthread
    [wbyq@wbyq ubuntu]$ ./a.out 
    STACK_MIN:16384
    线程栈空间:10240 kb
    修改后栈空间:8192 kb
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
  • 相关阅读:
    Web前端:Angular有哪些特征?什么时候使用Angular?
    【collections模块】之Counter
    c语言系统编程十三:Linux线程间的同步与互斥
    2.1 - 操作系统的作用、分类
    webpack proxy http-proxy-middleware header头丢失
    CC1链详解
    聊聊秒杀系统的设计(三)
    LangChain-v0.2 构建聊天机器人
    Hadoop2——Hadoop程序实现
    手把手教你清除多御安全浏览器的Cookie
  • 原文地址:https://blog.csdn.net/weixin_44453694/article/details/126354794