22-线程资源保护

8. 线程的资源(数据)保护
  8.1 进程的资源保护
    对于进程来说,由于每个进程空间是完全独立的,相互间不可能篡改对方进程空间的数据,所以进程空间
    内部的数据(资源)保护的非常到位,不需要加什么额外的保护机制
    只有当它们共享操作第三方资源时才会涉及到资源保护问题,比如共享操作第三方文件(或者共享内存)的数据时,才会使用到进程信号量这样的资源保护机制。

    在讲进程IPC的时候就说过,虽然进程信号量被划到“IPC”中,但是进程信号量的作用实际上是借助通信来实现资源(数据)保护

    对于进程来说,因为进程空间的独立性,因此进程资源的保护很到位,反倒是进程间共享数据很困难,因此OS提供了管道、消息队列等进程间通信机制

  8.2 线程的资源保护
    对于线程来说,由于进程内部的所有线程共享进程空间,因此线程间使用全局变量即可实现数据共享,数据
    通信的实现非常容易,不过数据共享越是容易,数据相互篡改的危险性就越高,因此对于线程来说,需要重点考虑如何保护资源(数据),防止相互篡改
    这就好比两个人关系非常好,好到穿一条裤子,好到什么秘密都分享,相互间共享秘密不是问题了,但是
    各自秘密的保护反倒成了问题
  所以:
    (1)进程:进程空间天然是独立的,因此进程间资源的保护是天然的(现成的),需要重点关心的进程间的通信
    (2)线程:多线程天然的共享进程空间,因此线程数据共享是天然的(现成的),需要重点关心的是资源的保护

  8.3 线程的资源保护机制

  C线程的资源保护机制有
  · 互斥锁
  · 信号量
  · 条件变量
  等

  这里重点介绍的是互斥锁、信号量、条件变量
  这三种资源保护机制,不仅在C线程里面有,在c++、java等的线程里面同样有,它们的实现原理和工作目的都是类似的

  

8.3.1 互斥锁
  互斥锁的作用就是用来实现互斥的
   尽管一个进程的互斥,另一个是线程的互斥,但是它们的原理都是一样的

(1)互斥锁使用的步骤
  1)定义一个互斥锁(变量)
  2)初始化互斥锁:预设互斥锁的初始值
  3)加锁解锁
  4)进程退出时销毁互斥锁

(2)互斥锁函数

  1)初始化互斥锁的函数
  (a)函数原型
  #include <pthread.h>

  int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

  · 功能:初始化定义的互斥锁
  什么是初始化,就是设置互斥锁所需要的值。

  · 返回值 总是返回0,所以这个函数不需要进行出错处理
  · 参数
  - mutex:互斥锁,需要我们自己定义。
  比如:pthread_mutex_t mutex;
    pthread_mutex_t是一个结构体类型,所以mutex实际上是一个结构体变量。
  - attr:互斥锁的属性
  设置NULL表示使用默认属性,除非我们想要实现一些互斥锁的特殊功能,否则默认属性就够用了
  如果你真想设置互斥锁属性的话,其设置方法与设置线程属性是类似的,由于很少用到

2)加锁解锁函数
(a)函数原型
  #include <pthread.h>
  int pthread_mutex_lock(pthread_mutex_t *mutex);
  int pthread_mutex_unlock(pthread_mutex_t *mutex);

  · 功能
  - pthread_mutex_lock:阻塞加锁
  如果锁没有解开时,当前线程尝试加锁时会阻塞,直到加锁成功为止。

  兄弟函数:pthread_mutex_trylock(pthread_mutex_t *mutex)
  非阻塞加锁,加锁成功是最好,如果不成功就错误返回,不会阻塞
  - pthread_mutex_unlock:解锁,解锁不会阻塞
  · 返回值成功返回0,失败返回错误号。
  · 参数
  mutex:需要加锁和解锁的互斥锁

3)pthread_mutex_destroy
(a)函数原型
  #include <pthread.h>

  int pthread_mutex_destroy(pthread_mutex_t *mutex);

(b)功能:销毁互斥锁
  所谓销毁,说白了就是删除互斥锁相关的数据,释放互斥锁数据所占用的各种内存资源。
(c)返回值:成功返回0,失败返回非零错误号

4)再说说互斥锁

(a)初始化互斥锁有两种方法
·   第1种:使用pthread_mutex_init实现

·   第2种:定义互斥锁时直接初始化实现
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  与
  pthread_mutex_init(&mutex, NULL);
  的功能是一样的,都是将互斥锁设置为快锁,什么是“快锁”,后面再解释。

#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>

#define SECON_PTH_NUMS 2

#define PTHEXIT -1



typedef struct pthread_arg{
    pthread_t tid;
    int pthno;
    int fd;
}ptharg;

//全局变量定义后,默认初始化为0
struct global_va{
    ptharg pth_arg[SECON_PTH_NUMS]; //结构体数组,每个元素会被当做参数传递给对应的次线程
    int pth_exit_flag[SECON_PTH_NUMS];//退出的状态
    pthread_attr_t attr; //存放线程新属性
    pthread_mutex_t mutex;//互斥锁

} glbva {.mutex=PTHREAD_MUTEX_INITIALIZER};

void print_err(char *str,int line,int err_no){
    printf("%d,%s:%s\n",line,str,strerror(err_no) );
    exit(-1);
}

//线程退出处理函数

void pth_exit_deal(void *arg){
    pthread_t tid=((ptharg *)arg)->tid;
    printf("pthread %lu exit!\n",tid );
}
void *pth_fun(void *pth_arg){
    //pthread_detach(pthread_self());//在次线程里分离
    //注册线程处理函数
    pthread_cleanup_push(pth_exit_deal,pth_arg);

    int fd= ((ptharg *)pth_arg)->fd;
    int pthno=((ptharg *)pth_arg)->pthno;

    pthread_t tid = ((ptharg *)pth_arg)->tid;
    printf("pthno=%d,pthread_id=%lu\n",pthno,tid);
    while(1){
        
        pthread_mutex_lock(&glbva.mutex);//加锁
        write(fd,"php",3);
        write(fd,"java",4);
        // sleep(1);
        pthread_mutex_unlock(&glbva.mutex);//解锁
        
        //检测退出状态
        if(glbva.pth_exit_flag[pthno] == PTHEXIT) break;
        
    }
    pthread_cleanup_pop(!0);
    printf("tid=%lu\n",pthread_self());
    //pthread_exit(NULL);
    return (void *)10;
}
void signal_fun(int signo){
    if(SIGALRM == signo){
        int i =0;
        for (;i<SECON_PTH_NUMS;i++){
            //pthread_cancel(glbva.pth_arg[i].tid);
            glbva.pth_exit_flag[i]=PTHEXIT; //设置为退出状态
        }
    }else if(SIGINT == signo){
        exit(0);
    }
}
void process_exit_deal(void){
    //销毁线程的属性设置
    int ret=0;
    ret=pthread_attr_destroy(&glbva.attr);
    if(ret !=0) print_err("pthread_attr_destroy error ",__LINE__,ret);

    //销毁互斥锁
    ret = pthread_mutex_destroy(&glbva.mutex);
    if(ret !=0) print_err("pthread_mutex_destroy error ",__LINE__,ret);
    printf("\nprocess exit\n");
}

int main(int argc, char const *argv[])
{
    pthread_t tid=0;
    int i=0;
    int ret =0;
    int fd =0;
    //注册进程退出函数.exit正常终止时,弹栈调用
    atexit(process_exit_deal);
    //初始化互斥锁
    ret = pthread_mutex_init(&glbva.mutex,NULL);
    if(ret !=0) print_err("pthread_mutex_init error ",__LINE__,ret);
    
    signal(SIGALRM,signal_fun);
    

    fd=open("./file",O_RDWR|O_CREAT|O_TRUNC,0664);
    if(fd ==-1) print_err("open file fail",__LINE__,errno);

    //初始化attr设置一些基本的初始值
    ret =pthread_attr_init(&glbva.attr);
    if(ret !=0) print_err("pthread_attr_init error ",__LINE__,ret);
    //设置分离属性
    ret=pthread_attr_setdetachstate(&glbva.attr, PTHREAD_CREATE_DETACHED);
    if(ret !=0) print_err("pthread_attr_setdetachstate error ",__LINE__,ret);

    for(;i<SECON_PTH_NUMS;i++){
        glbva.pth_arg[i].fd =fd;
        glbva.pth_arg[i].pthno=i;
        ret= pthread_create(&glbva.pth_arg[i].tid,&glbva.attr,pth_fun,(void *)&glbva.pth_arg[i]);
        if(ret !=0 ) print_err("pthread_creat err",__LINE__,ret);
        //pthread_detach(glbva.pth_arg[i].tid); //或者在次线程里写

    }
    signal(SIGINT,signal_fun);
    
    alarm(3);//不会阻塞的
    #if 0
    void *retval =NULL;

    for(i=0;i<SECON_PTH_NUMS;i++){
        pthread_join(glbva.pth_arg[i].tid,&retval);//阻塞等待次线程结束,接收返回值
        printf("retval =%ld\n",(long)retval );
    }
    #endif

    while(1){
        
        pthread_mutex_lock(&glbva.mutex);//加锁
        write(fd,"js",2);
        write(fd,"css",3);
        // sleep(1);
        pthread_mutex_unlock(&glbva.mutex); //解锁
        
    }
    return 0;
}

 



怎么理解pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ?
这句话的本来面目是:
struct mutex*** mutex = {**,**,**,...};
这个是典型的结构体变量的初始化,pthread_mutex_t其实就是对struct mutex*** typedef后
的类型,PTHREAD_MUTEX_INITIALIZER的宏值为{**,**,**,...}

(b)请问,以下写法对不对:
pthread_mutex_t mutex;
mutex = PTHREAD_MUTEX_INITIALIZER;

等价于
struct mutex*** mutex;
mutex = {**,**,**,...};

说白了这就是在尝试给结构体变量进行整体赋值,我们讲c时说过,结构体变量是不能够整体赋
值的,所以写法是错误的。
如果你想给结构体变量赋值的话,只能一个一个的给结构体成员赋值来实现。

其实我们调用pthread_mutex_init函数来初始化互斥锁时,这个函数设置初始值的方式,就是
给mutex这个结构体变量的成员一个一个的赋值来实现的。

所以说:
· 调用pthread_mutex_init函数来给mutex设置初始值时,实现的本质是结构体赋值。
· 使用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER方式给mutex设置初始值时,
实现的本质是结构体初始化。


· 有关PTHREAD_MUTEX_INITIALIZER宏

实际上除了这个宏外,还有两个宏,分别是:
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP


- PTHREAD_MUTEX_INITIALIZER:快速互斥锁(或叫阻塞互斥锁),简称快锁。
快锁的特点是:
+ 加锁不成功是会阻塞,如果不想阻塞必须使用pthread_mutex_trylock来加锁,而不是
pthread_mutex_lock。

+ 对于同一把快锁来说,不能多次加锁,否者会出错
+ 已经解锁的快锁也不能再次解锁,否者会出错

- PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:检错互斥锁
使用pthread_mutex_lock加锁时,如果加锁不成功不会阻塞,会直接出错返回。
加锁不成功就直接错误返回,所以才被称为“检错互斥锁”。

- PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:递归互斥锁。
特点:
+ 同一把锁可多次枷锁,每加一次锁,加锁次数就会加1
+ 解锁时,解锁的顺序刚好与加锁顺序相反,每解锁一次,加锁次数就会减1。

正是由于可以重复的加锁和解锁,所以才被称为递归加锁。

我们要求掌握的快锁,常用的也是快锁,至于检错锁和递归锁了解即可。

· pthread_mutex_init(&mutex, NULL)设置是什么锁
前面说过,当第二个参数为NULL时,默认设置的是快锁。

  如果你想通过pthread_mutex_init函数,将mutex初始化出“检错锁”和“递归锁”的话,我们必须通过
  第二个参数进行相应的属性设置来实现

  如果你真想使用“检错锁”和“递归锁”,建议还是使用直接初始化的方式,这样会更方便。
  pthread_mutex_t mutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
  pthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

 

posted @ 2018-10-14 15:36  H&K  阅读(598)  评论(0)    收藏  举报