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;

浙公网安备 33010602011771号