zoukankan      html  css  js  c++  java
  • 2019年8月14日星期三(系统编程) 线程属性 线程取消 线程的取消例程函数 线程同步互斥的方式

    2019814日星期三

    .线程属性  -> 分离属性

    1. 什么是分离属性?

    分离: 说明主线程不需要接合子线程  -> 不需要pthread_join子线程  -> 当主线程退出时,子线程还是会退出。

    非分离: 说明主线程需要接合子线程  -> 需要pthread_join子线程

    默认pthread_create()创建出来的线程都是非分离属性。

    2. 如何创建出分离属性的线程呢?

    思路: 添加分离属性到一个属性变量中,然后使用该属性变量去创建一个新的线程,那么这个线程就是一个分离的线程。

    1)定义一个属性变量  (数据类型: pthread_attr_t

       pthread_attr_t attr;

    2)初始化属性变量。  -> pthread_attr_init() -> man 3 pthread_attr_init

    功能: initialize thread attributes object 

    使用格式:

           #include <pthread.h>

           int pthread_attr_init(pthread_attr_t *attr);

           attr:需要初始化的属性变量

           返回值:

                  成功:0

                  失败:非0错误码

    3)设置分离属性到属性变量中   -> pthread_attr_setdetachstate()  -> man 3 pthread_attr_setdetachstate

    功能:set detach state attribute in thread attributes object

    使用格式:

           #include <pthread.h>

           int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

           attr:已初始化的属性变量的地址

           detachstate:PTHREAD_CREATE_DETACHED  -> 分离属性

                       PTHREAD_CREATE_JOINABLE  -> 非分离属性

           返回值:

                  成功:0

                  失败:非0错误码

    4)利用线程属性变量去创建一个分离属性的线程。

       pthread_create(&tid,&attr,...);

    5)销毁属性变量  -> pthread_attr_destroy()  ->  man 3 pthread_attr_destroy

    功能:destroy thread attributes object

    使用格式:

         #include <pthread.h>

       int pthread_attr_destroy(pthread_attr_t *attr);

           attr: 已初始化的属性变量

      返回值:

           成功:0

           失败:非0错误码

      练习1:验证一个分离属性线程,在主线程比自己提前退出时,该分离的线程会不会继续运行?  -> 不会继续运行。

    #include "head.h"

     

    void *routine(void *arg)

    {

           int i;

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

           {

                  printf("child %d ",i);

                  sleep(1);

           }

    }

     

    int main()

    {

           //1. 设置分离属性

           pthread_attr_t attr;

           pthread_attr_init(&attr);

           pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

          

           //2. 创建线程

           pthread_t tid;

           pthread_create(&tid,&attr,routine,NULL);

          

           //3. 主线程

           int i;

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

           {

                  printf("parent %d ",i);

                  sleep(1);

           }

          

           return 0;

    }

    3. 设置线程本身的属性为分离属性。  -> pthread_detach()  -> man 3 pthread_detach

    功能: detach a thread

    使用格式:

           #include <pthread.h>

           int pthread_detach(pthread_t thread);

           thread:需要分离的线程的ID号

           返回值:

                  成功:0

                  失败:非0错误码

    4. 获取线程自己的TID号。  -> pthread_self()  -> man 3 pthread_self

    功能:obtain ID of the calling thread

    使用格式:

           #include <pthread.h>

           pthread_t pthread_self(void);

           参数:无

           返回值: 线程的TID号

    例子:

    void *routine(void *arg)

    {

           pthread_detach(pthread_self());  -> 线程就会变成分离属性。

    }

     

    int main()

    {

           pthread_t tid;

           pthread_create(&tid,NULL,routine,NULL);

          

           pause();

    }

     

    . 线程的取消?

    1. 一般而言,都是主线程去控制子线程的状态。例如:主线程发送取消请求给子线程。

    函数: pthread_cancel()  -> man 3 pthread_cancel

    功能: send a cancellation request to a thread  -> 发送取消请求给线程。

    使用格式:

           #include <pthread.h>

           int pthread_cancel(pthread_t thread);

           thread:需要取消的线程的TID号。

           返回值:

                  成功:0

                  失败:错误码

    注意:

    收到取消请求就等价于是提前调用pthread_exit(),如果因为取消而退出,则不能把退出状态返回给主线程,但是线程主动退出pthread_exit()可以返回。

    #include "head.h"

    void *routine(void *arg)

    {

           int i;

           for(i=10;i>0;i--)

           {

                  printf("%d ",i);

                  sleep(1);

           }

          

           pthread_exit(NULL);

    }

     

    int main(int argc,char *argv[])

    {

           pthread_t tid;

           pthread_create(&tid,NULL,routine,NULL);  -> 普通线程默认是可以响应的!

          

           sleep(5);

           pthread_cancel(tid);

           pthread_join(tid,NULL);

          

           return 0;

    }

    2. 设置线程的取消响应的状态。  -> pthread_setcancelstate()  -> man 3 pthread_setcancelstate

    功能: set cancelability state

    使用格式:

           #include <pthread.h>

           int pthread_setcancelstate(int state, int *oldstate);

           state:

                  PTHREAD_CANCEL_ENABLE  -> 可以响应

                  PTHREAD_CANCEL_DISABLE  -> 不可以响应

           oldstate:原来的取消状态的值,不关心原来的状态,则填NULL。

    结论:

    If a cancellation request is received, it is blocked until cancelability is enabled.

    假设当前是不能响应取消请求的状态,但是这时收到一个取消请求,那么这个请求会一直阻塞等待,直到线程能响应取消请求为止才会被执行。

       练习2:验证结论。

    #include "head.h"

     

    void *routine(void *arg)

    {

           int i;

           pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);

          

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

           {

                  sleep(1);

                  printf("disable %d ",i);

           }

          

           pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);

          

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

           {

                  sleep(1);

                  printf("enable %d ",i);

           }

          

           pthread_exit(NULL);

    }

     

    int main(int argc,char *argv[])

    {

           pthread_t tid;

           pthread_create(&tid,NULL,routine,NULL);

          

           sleep(2);

           pthread_cancel(tid);

           printf("I send cancel to thread! ");

          

           pthread_join(tid,NULL);

          

           return 0;

    }

    3. 设置线程取消响应的类型  -> pthread_setcanceltype()  -> man 3 pthread_setcanceltype

    功能:set cancelability type

    使用格式:

           #include <pthread.h>

           int pthread_setcanceltype(int type, int *oldtype);

           type:

                  PTHREAD_CANCEL_DEFERRED  -> 延迟取消

                  PTHREAD_CANCEL_ASYNCHRONOUS  -> 立即取消  -> 默认创建的线程都是立即取消。

           oldtype:

                  原来的取消类型的值,不关心原来的状态,则填NULL。

    取消点函数有哪些?  -> man 7 pthreads

    Cancellation Points

    fgetc()

    fgets()

    fopen()

    fprintf()

    fputc()

    例子:

    #include "head.h"

     

    void *routine(void *arg)

    {

           pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);

          

           //for循环不属于取消点函数

           long i,j;

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

           {

                  for(j=0;j<100000;j++)

                  {

                        

                  }

           }

          

           while(1)

           {

                  fputc('a',stderr); //执行完这次取消点函数之后,再响应取消。

                  printf("helloworld! ");

           }

          

           pthread_exit(NULL);

    }

     

    int main(int argc,char *argv[])

    {

           pthread_t tid;

           pthread_create(&tid,NULL,routine,NULL);

           pthread_cancel(tid);

           printf("I send cancel to thread! ");

          

           pthread_join(tid,NULL);

           return 0;

    }

    . 线程的取消例程函数。

    1. 什么是线程取消例程函数?

    当线程收到取消请求时,先不要马上响应取消请求,而是要执行一个线程的例程函数,执行完这个函数之后再响应取消。

    一般而言,线程例程函数里面写一些释放公共资源的内容,例如:互斥锁,条件变量..

    2. 为什么要使用线程取消例程函数?

    为了防止线程带着一些系统公共资源一起被取消掉,如果带着资源而退出,则其他的线程就无法再次使用该资源。

    3. 如何实现?

    1)只需要在线程内部调用压栈函数。  -> pthread_cleanup_push()  -> man 3 pthread_cleanup_push

    功能: push thread cancellation clean-up handlers

    使用格式:

            #include <pthread.h>

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

           routine:线程取消例程函数   -> 以后收到取消请求,就会先执行该函数!

           arg:传递给线程取消例程函数的参数

           返回值:无

    回顾学习过的例程函数:

    信号处理函数:     void  fun(int sig)

    线程例程函数:     void *fun(void *arg)

    线程取消例程函数: void  fun(void *arg)

    2)将取消的例程函数弹栈

    功能: pop thread cancellation clean-up handlers

    使用格式:

           #include <pthread.h>

         void pthread_cleanup_pop(int execute);

           execute: 0  -> 在删除时,不执行该函数,直接删除。

                  非0  -> 在删除时,先执行一次该函数,再删除。

           返回值:无

    例子:

    pthread_cleanup_push(fun);

    ...

    ..   <- 取消请求         执行fun

    .    <- pthread_exit()  执行fun

    ...  <- return          不执行fun

     

    pthread_cleanup_pop(非0);  -> 执行fun

     

      例题:子线程收到主线程的取消时,不要马上取消,而是先打印"I recv cancel!",再取消。

     

    #include "head.h"

     

    void myfun(void *arg)

    {

           printf("I recv cancel! ");

    }

     

    void *routine(void *arg)

    {

           pthread_cleanup_push(myfun,NULL); //只要将来我收到取消的请求,就会先执行myfun。

          

           /* 线程持续10秒,在此期间,必定会收到取消请求。 */

           int i;

           for(i=0;i<10;i++)  //在10秒收到取消请求,会执行fun()

           {

                  printf("thread i = %d ",i);

                  sleep(1);

           }

          

           //pthread_exit(NULL); //  -> 还没有来得及删除例程就因为pthread_exit而退出,则执行例程函数。

           //return;   -> 还没有来得及删除例程就因为return而退出,则不会执行例程函数

           //sleep(3);

           printf("helloworld! ");

          

           pthread_cleanup_pop(1); //非0 -> 会执行

                                                     //0   -> 不会执行

    }

     

    int main(int argc,char *argv[])

    {

           pthread_t tid;

           pthread_create(&tid,NULL,routine,NULL);

          

           //sleep(3);

           //pthread_cancel(tid);

           //printf("I send cancel to thread! ");

          

           pthread_join(tid,NULL);

          

           return 0;

    }

    . 线程同步互斥的方式。

    1. 什么是同步互斥?为什么要处理同步互斥?

    同步互斥就是使得线程处理任务时有先后顺序,为了解决线程抢占资源问题。

    2. 处理同步互斥方式有哪些?

    信号量  -> 进程

    有名信号量  -> 进程

    无名信号量  -> 线程

    3. 有名信号量

    1)创建并打开一个有名信号量?  -> sem_open()  -> man 3 sem_open

    功能: initialize and open a named semaphore

    使用格式:

           #include <fcntl.h>          

            #include <sys/stat.h>       

            #include <semaphore.h>

           sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

           name:有名信号量的名字,要求必须以"/"开头,例如: /sem_test

           oflag:O_CREAT  -> 不存在则创建

                  O_EXCL  -> 存在则报错 

           mode:有名信号量的八进制权限,例如: 0777

           value:有名信号量的起始值

           返回值:

                  成功:有名信号量的地址 

                  失败:SEM_FAILED   -1

    2)有名信号量P/V操作

    P操作:  sem_wait()   资源数-1操作   -> man 3 sem_wait

    使用格式:

           #include <semaphore.h>

           int sem_wait(sem_t *sem);

           sem:有名信号量的地址

           返回值:

                  成功:0

                  失败:-1

    V操作:  sem_post()   资源数+1操作   -> man 3 sem_post

    使用格式:

            #include <semaphore.h>

           int sem_post(sem_t *sem);

           sem:有名信号量的地址

           返回值:

                  成功:0

                  失败:-1

    3)关闭有名信号量。  -> sem_close()  -> man 3 sem_close

    功能:close a named semaphore

    使用格式:

           #include <semaphore.h>

           int sem_close(sem_t *sem);

           sem:有名信号量的地址

           返回值:

                  成功:0

                  失败:-1

    4)删除有名信号量。  -> sem_unlink()  -> man 3 sem_unlink

    功能:remove a named semaphore

    使用格式:

           #include <semaphore.h>

           int sem_unlink(const char *name);

           name:有名信号量的名字

           返回值:

                  成功:0

                  失败:-1

       练习3:实现Jack进程与Rose进程使用共享内存来通信,要求使用有名信号量来处理同步互斥!

     

    Jack进程:

    #include "head.h"

     

    int main()

    {

           //1. 申请key值

           key_t key = ftok(".",10);

          

           //2. 根据key值申请共享内存ID号

           int shmid = shmget(key,2048,IPC_CREAT|0666);

          

           //3. 根据ID号申请共享内存的起始地址

           char *p = (char *)shmat(shmid,NULL,0);

          

           //4. 往共享内存中写入数据

           bzero(p,2048);

          

           //5. 创建并打开一个有名信号量

           sem_t *sem = NULL;

           sem = sem_open("/sem_test",O_CREAT,0777,0); //说明当前有名信号量的资源数为0

          

           while(1)

           {

                  //想开车进去

                  fgets(p,2048,stdin);

                 

                  //资源数+1

                  sem_post(sem);

                 

                  if(strncmp(p,"quit",4) == 0)

                  {

                         break;

                  }

           }

          

           return 0;

    }

     

    Rose进程:

    #include "head.h"

     

    int main()

    {

           //1. 申请key值

           key_t key = ftok(".",10);

          

           //2. 根据key值申请共享内存ID号

           int shmid = shmget(key,2048,IPC_CREAT|0666);

          

           //3. 根据ID号申请共享内存的起始地址

           char *p = (char *)shmat(shmid,NULL,0);

          

           //4. 创建并打开一个有名信号量

           sem_t *sem = NULL;

           sem = sem_open("/sem_test",O_CREAT,0777,0); //说明当前有名信号量的资源数为0

          

           //5. 不断读取共享内存的数据

           while(1)

           {

                  //当前资源数能-1不?

                  //能 -> 返回  不能 -> 阻塞

                  sem_wait(sem);

                 

                  //想开车走

                  printf("from shm:%s",p);

                 

                  if(strncmp(p,"quit",4) == 0)

                  {

                         break;

                  }     

           }

          

           sem_close(sem);

           sem_unlink("/sem_test");

           shmdt(p);

           shmctl(shmid,IPC_RMID,NULL);

           return 0;

    }

    4. 无名信号量  -> 既可以作用于进程,也可以作用于线程!

    1)由于无名信号量没有名字的,所以说不能打开,只能初始化。  -> sem_init()  -> man 3 sem_init

    功能: initialize an unnamed semaphore  -> 初始化未命名的信号量

    使用格式:

           #include <semaphore.h>

           int sem_init(sem_t *sem, int pshared, unsigned int value);

           sem: 无名信号量的地址

           pshared: 0 -> 作用于线程之间  -> 一般都是这个!

                   非0 -> 作用于进程之间

           value:无名信号量的起始值

           返回值:

                  成功:0

                  失败:-1

    2)无名信号量P/V操作

    P操作:  sem_wait()   资源数-1操作   -> man 3 sem_wait

    使用格式:

           #include <semaphore.h>

           int sem_wait(sem_t *sem);

           sem:无名信号量的地址

           返回值:

                  成功:0

                  失败:-1

    V操作:  sem_post()   资源数+1操作   -> man 3 sem_post

    使用格式:

            #include <semaphore.h>

           int sem_post(sem_t *sem);

           sem:无名信号量的地址

           返回值:

                  成功:0

                  失败:-1

    3)销毁无名信号量  -> sem_destroy()  -> man 3 sem_destroy

    功能:destroy an unnamed semaphore

    使用格式:

           #include <semaphore.h>

           int sem_destroy(sem_t *sem);

           sem:无名信号量的地址

           返回值:

                  成功:0

                  失败:-1

      练习4:创建5个线程,每一个线程任务都是一样。

             任务:将“helloworld”字符串每隔1S就打印一个字符  -> 完成任务:10秒

             要求5个子线程依次打印helloworld,不要同时打印。

     

       正确: helloworldhelloworldhelloworldhelloworldhelloworld

       错误: hhhhheeeeellllllllllooooowwwwwooooorrrrrlllllddddd

     

  • 相关阅读:
    C++静态库与动态库(转)
    Tornado异步
    Yacc与Lex
    云数据库
    linux如何查看端口被谁占用
    Innodb Double Write
    MySQL GTIDs(global transaction identifiers)
    Java并发编程:线程池的使用
    Oracle 建立索引及SQL优化
    解决redhat linux下IP地址可以ping通,域名无法ping通问题
  • 原文地址:https://www.cnblogs.com/zjlbk/p/11353875.html
Copyright © 2011-2022 走看看