linux线程的创建、退出、等待、取消、分离

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux线程的创建、退出、等待、取消、分离相关的知识,希望对你有一定的参考价值。

参考技术A

返回值:成功:0,错误:出错编号。
pthread不是Linux系统默认的库而是POSIX线程库。在Linux中将其作为一个库来使用,因此编译时需要加上-pthread以显式链接该库

返回线程ID
线程标识符在进程中是唯一的,即分别属于两不同进程的两个线程可能有相同的线程标识符

retval:返回信息

参数表:
thread: 要等待的线程的pid
retval:用来存储被等待线程的返回值
返回0:成功;返回错误号:失败
主线程阻塞自己,等待子线程结束,然后回收子线程资源

可以设置线程能否被取消和取消后是否立即执行

参数表
state:PTHREAD_CANCEL_DISABLE或者PTHREAD_CANCEL_ENABLE
oldstate:指针类型,上一次取消状态的指针,可设NULL

type:PTHREAD_CANCEL_ASYNCHRONOUS立即取消
PTHREAD_CANCEL_DEFERRED等待事件(如pthread_join时)才取消

在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死,只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。 因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收

返回0成功,错误号失败
分离后不可以再合并。该操作不可逆

综合以上要想让子线程总能完整执行(不会中途退出),

注:很多地方参照了黄茹老师主编的《Linux环境高级程序设计》

:线程

目录

一.引入

二.线程是什么

三.线程和进程的区别

        1)

        2)

        3)

四.线程API

        1)创建线程

        2)线程的退出

        3)设置线程被取消属性

        4)等待线程退出

        5)设置线程分离属性

        6)线程的互斥问题

                1、初始化线程互斥锁

                2、P操作/上锁

==#临界区==

                3、V操作/解锁

                4、销毁互斥锁

注意:

       7)同步问题

                1.初始化条件变量

                2.等待条件变量

                3.唤醒条件变量

                4.销毁条件变量


一.引入

        并发:指进程的执行方式,指在同一时间内有多个进程同时运行

        进程:程序关于某一数据集合的一次计算过程,是现代操作系统为了解决并发而特地引入的一个概念

        系统分配资源是以进程为单位 ==》 进程地址空间、PCB进程控制块

                进程 和 并发的出现大大提高了系统的执行效率

        进程的执行效率能否在提高呢

                以分时系统为例,每个进程依次执行一个 时间片 ==》 对CPU的持有时间是相等的

                想提高进程的执行效率 ==》 提高进程对CPU的持有时间

        ===》 线程:线程系统调度的最小单位

                系统调度不是以进程为单位,而是以 线程为单位

二.线程是什么

        进程 是程序关于某个数据集合的一次计算过程 ==》 执行态

        线程 是进程的一个执行分支

        如果一个进程只有一个执行分支,那么进程和线程等概念

                ===》 一个进程,可以拥有多个分支(多线程并发)

三.线程和进程的区别

        1)

                进程是操作系统分配资源的最小单位

                线程是比进程更小的活动单位

                系统调度的最小单位  ==》 进程拥有的线程数越多,对CPU的把持时间越长,处理效率越高

        2)

                进程 与 进程之间的地址空间是相互独立

                同一进程的不同线程间共用同一块进程地址空间

        3)

                进程间通信比较麻烦,需要用到都能访问的第三方空间作为通信媒介

                同一进程的不同线程间通信比较方便,只需要受保护(信号量samephore/互斥锁mutex)的全局变量就可以了

举个简单例子:

#include  <stdio.h>
#include  <stdlib.h>

void* test(void* arg)

    while(1)
   
        printf("I'm test,heheda\\n");
        sleep(1);
   

int  main(void)

    test(NULL);
    
    while(1)
    
        printf("I'm master,hahahahahaha\\n");    
        sleep(1);
    

程序输出结果为:

        原因:

                上述进程中只有一条执行分支,当该执行分支进入test函数时,遇上while(1)死循环出不来了

        如果想两个死循环的printf都被执行,要怎么办

                ==> 需要两个执行分支

四.线程API

Linux中使用的线程是基于POSIX提供的线程机制 ==》 POSIX Thread 简称 pthread,由libpthread.so库提供相应接口函数(自带该库)

        1)创建线程

NAME
       pthread_create - create a new thread
            创建一个新的线程(执行分支)
SYNOPSIS
       #include <pthread.h>
            
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

            @thread:指向一个pthread_t类型的变量空间,用来保存线程编号 tid
            @attr:指定线程属性,一般为NULL,表示默认属性(如果需要修改属性,通常是后期用相应接口函数修改)
            @start_routine:函数指针,指向一个 void*返回值,void*参数的函数,
                            即 指定新创建的线程要执行的函数,也被称为 线程函数
            @arg:void*地址,表示传递给线程函数的参数
        返回值:
            成功返回 0
            失败返回 !0,即失败的错误编码
                
       Compile and link with -pthread.  编译链接时请加上 -pthread

        练习:

                请用线程,将上述例子代码中的两个printf数据都打印

#include  <stdio.h>
#include  <unistd.h>
#include  <pthread.h>

void* test(void* arg)

    while(1)
   
        printf("I'm test,heheda\\n");
        sleep(1);
   

int  main(void)

    pthread_t tid = 0;
    int ret = 0;

    /* 创建一个新的线程,并指定线程去执行test函数 */
    ret = pthread_create(&tid,NULL,test,NULL);
    if(0 != ret)
    
        printf("pthread_create error:%d\\n",ret);
        return -1;
    

    while(1)
    
        printf("I'm master,hahahahahaha\\n");
        sleep(1);
    

编译:

运行结果:  

pstree命令以树状图查看进程信息  

        2)线程的退出

                线程退出有两种方式:

                        S.主线程退出则表示进程退出,因此所有依赖于该进程的线程也全部会结束

                        a.线程主动退出

                                线程函数执行结束

                                在线程任意地方调用pthread_exit函数,来结束线程本身

NAME
       pthread_exit - terminate calling thread

SYNOPSIS
       #include <pthread.h>
            pthread_exit用来结束一个线程
       void pthread_exit(void *retval);
            @retval:return value,指定一个线程返回值(请不要指定局部变量地址 why? 最好是返回malloc的地址)
       Compile and link with -pthread.

        线程退出后,与进程退出相同,也会留下“尸体”,此时可以由其他线程调用 pthread_join函数来等待线程退出并回收资源

                        b.被其他线程请求退出

其他线程可以发送一个退出请求给指定的线程,让被请求的线程退出

NAME
       pthread_cancel - send a cancellation request to a thread
            发送一个取消请求给指定的线程
SYNOPSIS
       #include <pthread.h>

       int pthread_cancel(pthread_t thread);
            @thread:线程号
          返回值:
                成功返回 0
                失败返回 !0,即失败的错误编码
       Compile and link with -pthread.

eg:

#include  <stdio.h>
#include  <unistd.h>
#include  <pthread.h>

void* test(void* arg)

    int a = *(int*)arg;    //将传递进来的变量变为私有变量

    while(1)
   
        printf("I'm test,heheda %d\\n",a);
        sleep(1);
   

int  main(void)

    pthread_t tid = 0;
    int ret = 0;
    int a = 10086;

    /* 创建一个新的线程,并指定线程去执行test函数 */
    ret = pthread_create(&tid,NULL,test,(void*)&a);
    if(0 != ret)
    
        printf("pthread_create error:%d\\n",ret);
        return -1;
    

    while(1)
    
        a++;
        if(a > 10090)
        
            printf("%d 你这个王八蛋,我要干掉你\\n",tid);
            pthread_cancel(tid);
        
        printf("I'm master,hahahahahaha\\n");
        sleep(1);
    

执行结果:

        但是,被请求的线程是否退出,取决于该线程的一个属性 PHTEAD_CANCELED,创建的线程默认是可被取消的(如上所示)

        可以通过函数 pthread_setcancelstate来设置这个属性

      

          3)设置线程被取消属性

NAME
       pthread_setcancelstate - set cancelability state
       and type

SYNOPSIS
       #include <pthread.h>
            pthread_setcancelstate设置线程取消状态
       int pthread_setcancelstate(int state, int *oldstate);
            @state:指定要设置的取消状态
                PTHREAD_CANCEL_ENABLE    可以被取消请求给取消
                PTHREAD_CANCEL_DISABLE    禁止被取消
            @oldstate:指向一个int空间用来保存原本的 装啊提
        返回值:
            成功 0
            失败 -1

eg:

同上个例子,修改线程函数代码,增加设置不可取消属性后

void* test(void* arg)

    int a = *(int*)arg;    //将传递进来的变量变为私有变量
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
    while(1)
   
        printf("I'm test,heheda %d\\n",a);
        sleep(1);
   

运行结果如下:

设置PTHREAD_CANCEL_DISABLE属性后的线程,就无法被外部线程给取消了!!!

        4)等待线程退出

        与进程退出相同,线程退出也可能留下尸体(未被回收的资源),此时需要其他线程调用pthread_join函数来负责回收

NAME
       pthread_join - join with a terminated thread
            等待一个线程退出,并回收其资源
SYNOPSIS
       #include <pthread.h>
            
       int pthread_join(pthread_t thread, void **retval);

            @thread:线程号tid,指定要等待哪个线程退出
            @retvel:二级指针,一级指针的地址,用来保存线程的退出码(即 pthread_exit的参数)
       Compile and link with -pthread.

        但是也并非每个线程退出,都需要其他线程来回收资源,这取决于线程的“分离属性”是否被设置

如果设置了分离属性,则线程退出时会自动回收资源

如果没设置分离属性,则必须要有人回收

  

      5)设置线程分离属性

NAME
       pthread_detach - detach a thread
            用来分离一个线程,使其在退出后,自动回收资源
SYNOPSIS
       #include <pthread.h>
            
       int pthread_detach(pthread_t thread);

            @thread:线程号,指定要设置分离属性的线程
       Compile and link with -pthread.

设置了detach属性的线程,是不会使pthread_join函数进入阻塞

        6)线程的互斥问题

         对于同一进程的不同线程来说,他们都是公用同一个进程地址空间,因此对这些线程来说第三方资源就太多了

        硬件、内核、系统、全局变量,因此也就存在线程就存在线程间数据传输的保护方式

                ===》等同于进程间通信所存在的问题:多个线程操作同一个共享资源时,资源状态存在不确定性

        特别对于那些不允许被多个进程/线程同时访问的内容 ==》 互斥资源

        在线程中,能起到“保护”作用的有两套机制

                a)信号量(同步互斥)

                        system_V 信号量

                        POSIX 信号量

                b)线程互斥锁(互斥)

                        用于同一进程的不同线程间,保护互斥资源的一种手段

                        在POSIX线程机制中,用pthread_mutex_t 类型(全局变量来描述一个互斥锁

                        互斥锁的实现类似于信号量

==注意==:需要额外安装man手册对POSIX的支持

sudo apt-get install manpages-posix-dev
sudo apt-get install manpages-posix

                1、初始化线程互斥锁

NAME
       pthread_mutex_init —initialize a mutex
            初始化一个线程互斥锁
SYNOPSIS
       #include <pthread.h>

       int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
            @mutex:pthread_mutext_t类型的指针(变量取地址),指定要初始化的线程互斥锁
            @attr:指定线程互斥锁的属性,一般为NULL,表示默认属性
       返回值:
                成功为0
                失败为!0,即错误编码
              
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

eg:

定义并初始化一把线程互斥锁

pthread_mutex_t  mutex;    //全局变量,互斥锁
    

    ret = pthread_mutex_init(&mutex,NULL);
    if(0 != ret)
   
        printf("mutex inint error\\n");
        return -1;
   

                2、P操作/上锁

NAME
       pthread_mutex_lock,  pthread_mutex_trylock,  pthread_mutex_unlock  
       lock and unlock a mutex

SYNOPSIS
       #include <pthread.h>
          
 上锁,能锁就锁,不能锁就一直等
       int pthread_mutex_lock(pthread_mutex_t *mutex);
            尝试上锁,能锁就锁,不能锁就返回失败
       int pthread_mutex_trylock(pthread_mutex_t *mutex);

==#临界区==

                3、V操作/解锁

解锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);

                4、销毁互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

                在线程中,线程互斥锁只能用于互斥,无法同步,那线程中如何实现多线程同步执行呢

注意:

        线程结束时,一定记住放锁,切勿带锁死亡

        7)同步问题

        线程的同步问题被称为 条件变量,在POSIX的线程机制中用 pthread_cond_t 来描述一个条件变量

        条件变量的概念:

                a) 在多线程程序设计中,我们用条件变量表示某一选定的条件

                        比如:

                                主线程要等分子线程循环10次后,再开始执行

                b)条件变量上的操作:初始化、等待、唤醒

        大致实现机制如下:

(伪代码)

void* thread_fun1(void* arg)

    wait; //等待某个条件变量

void* thread_fun2(void* arg)

    ……
    if(xx条件)
    
        signal;//唤醒指定条件变量
    

        条件变量用于多线程同步,因此条件变量本身是一个 共享资源 ====》 需要互斥锁保护

修改后的伪代码如下

void* thread_fun1(void* arg)

    lock;
    wait; //解锁 并 等待某个条件变量(线程条件变量等待的本身会具备解锁功能)
    unlock();

void* thread_fun2(void* arg)

    ……
    if(xx条件)
    
        lock;
        signal;//唤醒指定条件变量
        unlock;
   

        

        条件变量API

                1.初始化条件变量

NAME
       pthread_cond_destroy, pthread_cond_init  —  destroy  and  initialize
       condition variables

SYNOPSIS
       #include <pthread.h>

             pthread_cond_init用来初始化一个条件变量
       int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
            @cond:指定要初始化的条件变量
            @attr:指定条件变量属性,一般为NULL

eg:

pthread_cond_t cond;

    pthread_cond_init(&cond,NULL);

                2.等待条件变量

NAME
       pthread_cond_timedwait, pthread_cond_wait — wait on a condition

SYNOPSIS
       #include <pthread.h>
                pthread_cond_wait等待一个条件变量(死等)
        int pthread_cond_wait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex);

            @cond:指定要等待的条件变量
            @mutex:指定用来保护条件变量的那个互斥锁
        
               
                pthread_cond_timedwait 超时等待,具体用法,结合cond和POSIX信号量一起看,你就懂了,而且懂了两个知识
       int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict abstime);

==线程的条件变量在进入等待之前,会先将保护自己的互斥锁给解锁,一遍其他线程来访问条件变量并唤醒正在等待的线程==

               

                 3.唤醒条件变量

NAME
       pthread_cond_broadcast, pthread_cond_signal — broadcast or signal  a
       condition

SYNOPSIS
       #include <pthread.h>
            pthread_cond_broadcast广播,唤醒条件变量上所有进入等待的线程
       int pthread_cond_broadcast(pthread_cond_t *cond);

            pthread_cond_signal 唤醒条件变量上一个正在等待的线程
       int pthread_cond_signal(pthread_cond_t *cond);

                4.销毁条件变量

             pthread_cond_destroy 用来销毁指定的条件变量
    int pthread_cond_destroy(pthread_cond_t *cond);

以上是关于linux线程的创建、退出、等待、取消、分离的主要内容,如果未能解决你的问题,请参考以下文章

C++笔记--Linux编程(13)-守护进程-线程

linux多线程控制详述

Linux-线程终止-线程等待-线程分离-线程安全

Linux:详解多线程(线程概念线程控制—线程创建线程终止线程等待)

:线程

:线程