2019年8月14日星期三(系统编程)

Posted zjlbk

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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\\n",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\\n",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\\n",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\\n",i);

      

      

       pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);

      

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

      

              sleep(1);

              printf("enable %d\\n",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!\\n");

      

       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!\\n");

      

      

       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!\\n");

      

       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!\\n");

 

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\\n",i);

              sleep(1);

      

      

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

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

       //sleep(3);

       printf("helloworld!\\n");

      

       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!\\n");

      

       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

 

作业1: 练习4。

作业2: 昨晚的第三题。

作业3: 整理所有学习过的函数。

以上是关于2019年8月14日星期三(系统编程)的主要内容,如果未能解决你的问题,请参考以下文章

2019年8月22日 星期四(杂谈)

2019年8月22日 星期四(怎样成为PHP大牛)

官方通知2019智慧教育博览会_2019北京教育装备展

linx基础篇-时间管理 简述

部署虚拟环境linux系统 2019年7月15日星期一 第二课

2019年7月26日星期五(文件IO)