• Linux 系统编程 学习:9-线程:线程的创建、回收与取消


    概念

    基础概念:

    • 线程是cpu或操作系统调度的基本单位。线程大部分的资源是共享的,仅仅申请了自己的栈、空间。
    • 线程是进程内部的一个执行分支,线程量级很小。
    • 在程序中创建线程,可以提高效率,进程内线程越多,争夺到CPU的概率就越大,执行代码的概率就越大(有一个度)。
    • 线程可以解决很多问题,而不会像进程一样有那么多的开销。
    • 在线程中需要注意同步的问题。一个线程的bug很可能会引起该进程的崩溃。

    线程与进程的内存分布不同:

    • 每个进程在创建的时候都申请了新的内存空间以存储代码段数据段BSS段堆栈空间,并且这些的空间的初始化值是父进程空间的,父子进程在创建后不能互访资源。
    • 而每个新创建的线程则仅仅申请了自己的栈、空间;与同进程的其他线程共享该进程的其他数据空间包括代码段数据段BSS段堆以及打开的库、mmap映射的文件与共享的空间,使得同进程下的线程共享数据十分的方便,只需借助这些共享区域即可,但也有问题即是同步问题。

    线程开发基本步骤

    在接下来的开发中,我们会介绍有关的函数;

    所有线程是在<pthread.h>中,且编译时需要链接pthread库;下面不再说明。
    同时,如果没有特殊说明,所有的函数在失败时都返回错误号(error number)。

    线程的创建、回收与取消

    创建线程

    c
    intpthread_create(pthread_t *thread, constpthread_attr_t *attr,
                       void *(*start_routine) (void *), void *arg);
    

    描述:创建一个线程,线程创建成功以后,开始执行指定的函数。

    默认情况下,一个线程所使用的内存资源在应用pthread_join调用之前不会被重新分配,所以对于每个线程必须调用一次pthread_join函数(分离线程除外)。

    参数解析:

    thread:存放线程ID号的对象

    attr:创建线程时设置的有关属性,可为空(这里先略过,我们会在后面专门讲到)

    start_routine:线程执行的函数入口

    arg:执行函数时附带的参数,可为空

    返回值:成功返回0,从thread可以获取到线程ID;失败返回错误号。

    回收线程

    c
    intpthread_join(pthread_t thread, void **retval);
    

    描述:阻塞等待一个线程,并回收其资源。

    默认情况下,新创建的线程是joinable的,线程退出后,需对其进行pthread_join操作。

    如果不关心线程的返回值,我们可以告诉系统,当线程退出时,自动释放线程资源(后面我们会讲到)

    参数解析:

    thread:指定等待的线程号

    retval:非空时,获取由pthread_exit(void *retval);函数传过来的结果。

    返回值:成功返回0,失败返回error number。

    提出一个终止线程的请求

    c
    intpthread_cancel(pthread_t thread);
    

    描述:一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。

    同一进程的线程间,pthread_cancel向另一线程发终止信号。如何处理Cancel信号则由目标线程自己决定:忽略、立即终止、继续运行至Cancelation-point(取消点),由不同的Cancelation状态(pthread_setcancelstate函数设置状态)决定。

    被取消线程可以调用pthread_testcancel,让内核去检测是否需要取消当前线程。被取消的线程退出时总是返回-1(常数值PTHREAD_CANCELED)。

    如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。 请务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点意外,pthread标准还指定了几个取消点。测试退出点,就是测试cancel信号

    返回值: 发送成功返回0(不意味着thread会终止);失败返回错误号。

    一个取消线程的例程
    c
    #include<stdio.h>
    #include<stdlib.h>
    #include<pthread.h>
    
    void *thread_fun(void *arg){
        int i=1;
        printf("thread start 
    ");
        while(1)
        {
            // pthread_testcancel(); 如果没有这句话,那么线程不会结束。
            i++;
        }
        return (void *)0;
    }
    
    intmain(int argc, char* argv){
        void *ret = NULL;
        int iret = 0;
        pthread_t tid;
        pthread_create(&tid, NULL, thread_fun, NULL);
        sleep(1);
        pthread_cancel(tid);//取消线程
        pthread_join(tid, &ret);
        printf("thread 3 exit code %d
    ", (int)ret);
        return 0;
    }
    

    线程取消

    线程在收到取消请求(pthread_cancel)后会继续运行,直到到达某个取消点(CancellationPoint)。

    取消点:线程检查是否被取消并按照请求进行动作的一个位置。

    pthreads标准指定了几个取消点,其中包括:
    (1)通过pthread_testcancel调用以编程方式建立线程取消点。
    (2)线程等待pthread_cond_waitpthread_cond_timewait()中的特定条件。 (错误的程序设计可能会在取消时导致死锁)
    (3)被sigwait()阻塞的函数
    (4)一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。

    设置一个线程能否被取消

    c
    intpthread_setcancelstate(int state, int *oldstate)

    描述:设置一个线程能否被取消

    参数解析:

    state: 状态

    • PTHREAD_CANCEL_ENABLE(缺省动作,收到信号后设为CANCLED状态)
    • PTHREAD_CANCEL_DISABLE(忽略CANCEL信号继续运行)

    old_state: 旧状态容器,如果不为 NULL则存入原来的Cancel状态。

    设置本线程取消动作的执行时机

    c
    intpthread_setcanceltype(int type, int *oldtype)

    描述:设置本线程取消动作的执行时机(仅当Cancel状态为Enable时有效)

    参数解析:

    type : 取消类型

    • PTHREAD_CANCEL_DEFFERED (默认,收到信号后继续运行至下一个取消点再退出)
    • PTHREAD_CANCEL_ASYCHRONOUS, (立即执行取消动作——退出)

    oldtype : 旧状态容器,如果不为NULL则存入运来的取消动作类型值。

    手动创建取消点

    c
    voidpthread_testcancel(void)

    描述: 手动创建一个取消点,但线程设置了PTHREAD_CANCEL_ENABLEPTHREAD_CANCEL_DEFFERED属性,且已经有线程发送了取消本线程的请求时,退出;否则直接返回。

    注意:由于此函数在线程内执行,执行的位置就是线程退出的位置,所以在执行此函数以前,线程内部的相关资源申请一定要释放掉,很容易造成内存泄露

    总结:
    1)线程可以调用pthread_setcancelstate()设置被取消的,或者不能被取消的
    2)线程的取消的本质是处理取消信号
    3)取消线程可以马上进行的,也可以在取消点才取消 (pthread_setcanceltype()

    若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL 指令后,使用 pthread_join 函数, 等待指定的线程已经完全退出以后, 再继续执行; 否则,很容易产生 “段错误”。

    线程遗嘱

    遗嘱机制一般用于释放一些资源,比如释放锁,以免其它的线程永远 也不能获得锁,而造成死锁。

    c
    voidpthread_cleanup_push(void (*routine)(void *), void *arg);
    voidpthread_cleanup_pop(int execute);
    

    登记执行压栈清理函数

    c
    voidpthread_cleanup_push(void (*routine)(void *), void *arg);
    

    描述:登记执行压栈清理函数的操作。

    参数解析:

    routine:清理函数

    arg:清理函数的有关参数

    当以下描述的情况发生时自动调用的函数:

    • 线程调用pthread_exit()函数,而不是直接return.
    • 响应取消请求时,也就是有其它的线程对该线程调用pthread_cancel()函数而到达取消条件时。
    • 本线程调用pthread_cleanup_pop()函数,并且其参数非0。

    从栈中删除清理函数的操作

    c
    voidpthread_cleanup_pop(int execute);
    

    描述:从栈中删除清理函数的操作。

    参数解析:

    execute:标志位,当其非0时,执行pthread_cleanup_push中登记好的函数;否则,将其出栈,不执行。

    例程

    2条线程使用互斥锁(后面会讲到)抢占一个资源,当占有锁的其中一个线程被意外中断时之前写好了释放锁的遗嘱,另外一条能够正常拿到锁。

    c
     
    #include<stdio.h>
    #include<unistd.h>
    
    #include<stdlib.h>
    #include<pthread.h>
    pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;
    
    voidclean_handler(void *arg){
        pthread_mutex_unlock((pthread_mutex_t *)arg); // 防止死锁,所以在这里添加解锁操作
        printf("unlocked from clean_handler push by thread_fun1
    ");
    }
    
    void *thread_fun1(void *arg){
        int i=1;
        printf("thread1 start 
    ");
        pthread_cleanup_push(clean_handler, &mutex_x);//提前登记线程被取消后需要处理的事情
        pthread_mutex_lock(&mutex_x);
        printf("thread_fun1 locked.
    ");
        while(1)
        {
            pthread_testcancel(); //如果没有这句话,那么线程不会结束。
            i++;
        }
        pthread_mutex_unlock(&mutex_x);
        printf("thread_fun1 unlocked
    ");
        pthread_cleanup_pop(0);
        return (void *)0;
    }
    
    
    void *thread_fun2(void *arg){
        int i=1;
        printf("thread2 start 
    ");
        for (i = 0; i < 6; ++i)
        {
            pthread_mutex_lock(&mutex_x);
            printf("thread_fun2 locked.
    ");
            sleep(1);
            pthread_mutex_unlock(&mutex_x);
            printf("thread_fun2 unlocked
    ");
        }
        printf("thread2 end 
    ");
        return (void *)0;
    }
    
    intmain(int argc, char* argv){
        void *ret = NULL;
        int iret = 0;
        pthread_t tid1;
        pthread_t tid2;
        pthread_create(&tid1, NULL, thread_fun1, NULL);
        pthread_create(&tid2, NULL, thread_fun2, NULL);
        sleep(2);
        pthread_cancel(tid1);//取消线程
        pthread_join(tid1, &ret);
    
        iret =(int)( (int*)ret);
        printf("thread 3 exit code %d
    ", iret);
        while(1);
        return 0;
    }

    概念

    基础概念:

    • 线程是cpu或操作系统调度的基本单位。线程大部分的资源是共享的,仅仅申请了自己的栈、空间。
    • 线程是进程内部的一个执行分支,线程量级很小。
    • 在程序中创建线程,可以提高效率,进程内线程越多,争夺到CPU的概率就越大,执行代码的概率就越大(有一个度)。
    • 线程可以解决很多问题,而不会像进程一样有那么多的开销。
    • 在线程中需要注意同步的问题。一个线程的bug很可能会引起该进程的崩溃。

    线程与进程的内存分布不同:

    • 每个进程在创建的时候都申请了新的内存空间以存储代码段数据段BSS段堆栈空间,并且这些的空间的初始化值是父进程空间的,父子进程在创建后不能互访资源。
    • 而每个新创建的线程则仅仅申请了自己的栈、空间;与同进程的其他线程共享该进程的其他数据空间包括代码段数据段BSS段堆以及打开的库、mmap映射的文件与共享的空间,使得同进程下的线程共享数据十分的方便,只需借助这些共享区域即可,但也有问题即是同步问题。

    线程开发基本步骤

    在接下来的开发中,我们会介绍有关的函数;

    所有线程是在<pthread.h>中,且编译时需要链接pthread库;下面不再说明。
    同时,如果没有特殊说明,所有的函数在失败时都返回错误号(error number)。

    线程的创建、回收与取消

    创建线程

    c
    intpthread_create(pthread_t *thread, constpthread_attr_t *attr,
                       void *(*start_routine) (void *), void *arg);
    

    描述:创建一个线程,线程创建成功以后,开始执行指定的函数。

    默认情况下,一个线程所使用的内存资源在应用pthread_join调用之前不会被重新分配,所以对于每个线程必须调用一次pthread_join函数(分离线程除外)。

    参数解析:

    thread:存放线程ID号的对象

    attr:创建线程时设置的有关属性,可为空(这里先略过,我们会在后面专门讲到)

    start_routine:线程执行的函数入口

    arg:执行函数时附带的参数,可为空

    返回值:成功返回0,从thread可以获取到线程ID;失败返回错误号。

    回收线程

    c
    intpthread_join(pthread_t thread, void **retval);
    

    描述:阻塞等待一个线程,并回收其资源。

    默认情况下,新创建的线程是joinable的,线程退出后,需对其进行pthread_join操作。

    如果不关心线程的返回值,我们可以告诉系统,当线程退出时,自动释放线程资源(后面我们会讲到)

    参数解析:

    thread:指定等待的线程号

    retval:非空时,获取由pthread_exit(void *retval);函数传过来的结果。

    返回值:成功返回0,失败返回error number。

    提出一个终止线程的请求

    c
    intpthread_cancel(pthread_t thread);
    

    描述:一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。

    同一进程的线程间,pthread_cancel向另一线程发终止信号。如何处理Cancel信号则由目标线程自己决定:忽略、立即终止、继续运行至Cancelation-point(取消点),由不同的Cancelation状态(pthread_setcancelstate函数设置状态)决定。

    被取消线程可以调用pthread_testcancel,让内核去检测是否需要取消当前线程。被取消的线程退出时总是返回-1(常数值PTHREAD_CANCELED)。

    如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。 请务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点意外,pthread标准还指定了几个取消点。测试退出点,就是测试cancel信号

    返回值: 发送成功返回0(不意味着thread会终止);失败返回错误号。

    一个取消线程的例程
    c
    #include<stdio.h>
    #include<stdlib.h>
    #include<pthread.h>
    
    void *thread_fun(void *arg){
        int i=1;
        printf("thread start 
    ");
        while(1)
        {
            // pthread_testcancel(); 如果没有这句话,那么线程不会结束。
            i++;
        }
        return (void *)0;
    }
    
    intmain(int argc, char* argv){
        void *ret = NULL;
        int iret = 0;
        pthread_t tid;
        pthread_create(&tid, NULL, thread_fun, NULL);
        sleep(1);
        pthread_cancel(tid);//取消线程
        pthread_join(tid, &ret);
        printf("thread 3 exit code %d
    ", (int)ret);
        return 0;
    }
    

    线程取消

    线程在收到取消请求(pthread_cancel)后会继续运行,直到到达某个取消点(CancellationPoint)。

    取消点:线程检查是否被取消并按照请求进行动作的一个位置。

    pthreads标准指定了几个取消点,其中包括:
    (1)通过pthread_testcancel调用以编程方式建立线程取消点。
    (2)线程等待pthread_cond_waitpthread_cond_timewait()中的特定条件。 (错误的程序设计可能会在取消时导致死锁)
    (3)被sigwait()阻塞的函数
    (4)一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。

    设置一个线程能否被取消

    c
    intpthread_setcancelstate(int state, int *oldstate)

    描述:设置一个线程能否被取消

    参数解析:

    state: 状态

    • PTHREAD_CANCEL_ENABLE(缺省动作,收到信号后设为CANCLED状态)
    • PTHREAD_CANCEL_DISABLE(忽略CANCEL信号继续运行)

    old_state: 旧状态容器,如果不为 NULL则存入原来的Cancel状态。

    设置本线程取消动作的执行时机

    c
    intpthread_setcanceltype(int type, int *oldtype)

    描述:设置本线程取消动作的执行时机(仅当Cancel状态为Enable时有效)

    参数解析:

    type : 取消类型

    • PTHREAD_CANCEL_DEFFERED (默认,收到信号后继续运行至下一个取消点再退出)
    • PTHREAD_CANCEL_ASYCHRONOUS, (立即执行取消动作——退出)

    oldtype : 旧状态容器,如果不为NULL则存入运来的取消动作类型值。

    手动创建取消点

    c
    voidpthread_testcancel(void)

    描述: 手动创建一个取消点,但线程设置了PTHREAD_CANCEL_ENABLEPTHREAD_CANCEL_DEFFERED属性,且已经有线程发送了取消本线程的请求时,退出;否则直接返回。

    注意:由于此函数在线程内执行,执行的位置就是线程退出的位置,所以在执行此函数以前,线程内部的相关资源申请一定要释放掉,很容易造成内存泄露

    总结:
    1)线程可以调用pthread_setcancelstate()设置被取消的,或者不能被取消的
    2)线程的取消的本质是处理取消信号
    3)取消线程可以马上进行的,也可以在取消点才取消 (pthread_setcanceltype()

    若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL 指令后,使用 pthread_join 函数, 等待指定的线程已经完全退出以后, 再继续执行; 否则,很容易产生 “段错误”。

    线程遗嘱

    遗嘱机制一般用于释放一些资源,比如释放锁,以免其它的线程永远 也不能获得锁,而造成死锁。

    c
    voidpthread_cleanup_push(void (*routine)(void *), void *arg);
    voidpthread_cleanup_pop(int execute);
    

    登记执行压栈清理函数

    c
    voidpthread_cleanup_push(void (*routine)(void *), void *arg);
    

    描述:登记执行压栈清理函数的操作。

    参数解析:

    routine:清理函数

    arg:清理函数的有关参数

    当以下描述的情况发生时自动调用的函数:

    • 线程调用pthread_exit()函数,而不是直接return.
    • 响应取消请求时,也就是有其它的线程对该线程调用pthread_cancel()函数而到达取消条件时。
    • 本线程调用pthread_cleanup_pop()函数,并且其参数非0。

    从栈中删除清理函数的操作

    c
    voidpthread_cleanup_pop(int execute);
    

    描述:从栈中删除清理函数的操作。

    参数解析:

    execute:标志位,当其非0时,执行pthread_cleanup_push中登记好的函数;否则,将其出栈,不执行。

    例程

    2条线程使用互斥锁(后面会讲到)抢占一个资源,当占有锁的其中一个线程被意外中断时之前写好了释放锁的遗嘱,另外一条能够正常拿到锁。

    c
     
    #include<stdio.h>
    #include<unistd.h>
    
    #include<stdlib.h>
    #include<pthread.h>
    pthread_mutex_t mutex_x=PTHREAD_MUTEX_INITIALIZER;
    
    voidclean_handler(void *arg){
        pthread_mutex_unlock((pthread_mutex_t *)arg); // 防止死锁,所以在这里添加解锁操作
        printf("unlocked from clean_handler push by thread_fun1
    ");
    }
    
    void *thread_fun1(void *arg){
        int i=1;
        printf("thread1 start 
    ");
        pthread_cleanup_push(clean_handler, &mutex_x);//提前登记线程被取消后需要处理的事情
        pthread_mutex_lock(&mutex_x);
        printf("thread_fun1 locked.
    ");
        while(1)
        {
            pthread_testcancel(); //如果没有这句话,那么线程不会结束。
            i++;
        }
        pthread_mutex_unlock(&mutex_x);
        printf("thread_fun1 unlocked
    ");
        pthread_cleanup_pop(0);
        return (void *)0;
    }
    
    
    void *thread_fun2(void *arg){
        int i=1;
        printf("thread2 start 
    ");
        for (i = 0; i < 6; ++i)
        {
            pthread_mutex_lock(&mutex_x);
            printf("thread_fun2 locked.
    ");
            sleep(1);
            pthread_mutex_unlock(&mutex_x);
            printf("thread_fun2 unlocked
    ");
        }
        printf("thread2 end 
    ");
        return (void *)0;
    }
    
    intmain(int argc, char* argv){
        void *ret = NULL;
        int iret = 0;
        pthread_t tid1;
        pthread_t tid2;
        pthread_create(&tid1, NULL, thread_fun1, NULL);
        pthread_create(&tid2, NULL, thread_fun2, NULL);
        sleep(2);
        pthread_cancel(tid1);//取消线程
        pthread_join(tid1, &ret);
    
        iret =(int)( (int*)ret);
        printf("thread 3 exit code %d
    ", iret);
        while(1);
        return 0;
    }
  • 相关阅读:
    轻松自动化---selenium-webdriver(python) (八)
    轻松自动化---selenium-webdriver(python) (七)
    轻松自动化---selenium-webdriver(python) (六)
    轻松自动化---selenium-webdriver(python) (五)
    轻松自动化---selenium-webdriver(python) (四)
    轻松自动化---selenium-webdriver(python) (三)
    轻松自动化---selenium-webdriver(python) (二)
    轻松自动化---selenium-webdriver(python) (一)
    容器在 Weave 中如何通信和隔离?- 每天5分钟玩转 Docker 容器技术(65)
    Weave 网络结构分析
  • 原文地址:https://www.cnblogs.com/wt88/p/15100307.html
Copyright © 2020-2023  润新知