Linux多线程可重入函数

简介:

Reentrant和Thread-safe

在单线程程序中,整个程序都是顺序执行的,一个函数在同一时刻只能被一个函数调用,但在多线程中,由于并发性,一个函数可能同时被多个函数调用,此时这个函数就成了临界资源,很容易造成调用函数处理结果的相互影响,如果一个函数在多线程并发的环境中每次被调用产生的结果是不确定的,我们就说这个函数是"不可重入的"/"线程不安全"的。为了解决这个问题,POSIX多线程库提出了一种机制,用来解决多线程环境中的线程数据私有化问题,这套机制的主要思想是利用同步和互斥维护一个同名不同值的表,这个表会维护每个线程自己的资源地址,表面上是同一个变量,实质上这个变量在不同的线程中的地址是不一样,这样就保证了每个线程其实都在使用自己的资源,实现了"thread-safe"。

其实,随着多线程程序的逐渐流行,除了这种利用系统机制保护线程私有数据的方法,还有一部分人重新编写了一些多线程库函数,这些函数的主要特点就是实现了算法和数据的分离,函数内部只负责实现算法,需要的数据由线程传入,这样就保证了函数的多线程安全,eg

 
 
  1. char *asctime(const struct tm *tm); 
  2. char *asctime_r(const struct tm *tm, char *buf);    //这个就是asctime的thread-safe版,有_r后缀 

但由于接口不同,完全重写的函数推广尚需时日。

当下用的更多的是使用_REENTRANT来在原来的函数的基础上改造,如果编译的时候定义了这个宏,相关的库函数就会被编译成"thread-safe"的版本。

模型

如果要查看这些函数的man手册,可以安装相关的man手册

 
 
  1. pthread_key_t key           //创建用于保护线程私有资源的 
  2. keypthread_once_t once_key     //创建用于初始化key的once_key,要求用PTHREAD_INIT_ONCE来赋值,否则结果不确定 
  3.  
  4. pthread_key_create()        //创建 
  5. keypthread_once()              //初始化 
  6. keypthread_getspedifc()        //从key表中获得线程私有资源的地址 
  7.  
  8. pthread_setspedifc()        //将线程私有资源的地址放到key中... 

例子

表面上每个函数调用了reverse()都会得到rev的地址,其实这个rev地址在不同的线程中并不相同,一旦一个线程调用了reverse()函数,函数首先会到key标识的表中去搜索这个线程以前是否调用过这个函数,如果调用过,就将表中属于这个线程的rev地址返回,如果没有,就分配rev,并将该线程和它的专属rev地址注册到表中,这样就把reverse()打造成了一个可重入的函数。

 
 
  1. #include<stdio.h> 
  2. #include<pthread.h> 
  3. #include<stdlib.h> 
  4. #include<string.h> 
  5.  
  6. pthread_key_t key
  7. pthread_once_t once_key=PTHREAD_ONCE_INIT; 
  8.  
  9. #ifdef _REENTRANT 
  10. void myDestructor(void*p){ 
  11.     free(p); 
  12. void myCreateKey(void){    //创建key 
  13.     pthread_key_create(&key,myDestructor); 
  14. #endif 
  15.  
  16. char* reverse(char* buf,int len){ 
  17. #ifdef _REENTRANT 
  18.     //初始化key 
  19.     pthread_once(&once_key,myCreateKey);  //从key中获取一个thread-specific的数据 
  20.     char* rev=(char*)pthread_getspecific(key); 
  21.     if(NULL==rev){ 
  22.         rev=(char*)malloc(len+1);        //将thread-specific的数据放到key中 
  23.         pthread_setspecific(key,rev); 
  24.     } 
  25. #else 
  26.     static char rev[100]; 
  27. #endif 
  28.     bzero(rev,sizeof(rev));    //翻转buf 
  29.     while(len--) 
  30.         rev[len]=*buf++; 
  31.     return rev; 
  32. void* fcn1(void* p){ 
  33.     while(1){ 
  34.         char buf[100]="123456789"
  35.         printf("[%lu]:%s\n",pthread_self(),buf); 
  36.         char* rev=reverse(buf,strlen(buf)); 
  37.         sleep(1); 
  38.         printf("[%lu]:%s\n",pthread_self(),rev); 
  39.     } 
  40.  
  41. void* fcn2(void* p){ 
  42.     while(1){ 
  43.         char buf[100]="abcdef"
  44.         printf("[%lu]:%s\n",pthread_self(),buf);         
  45.         char* rev=reverse(buf,strlen(buf)); 
  46.         sleep(2);         
  47.         printf("[%lu]:%s\n",pthread_self(),rev); 
  48.  
  49.     } 
  50. }int main(int argc, const char *argv[]){ 
  51.     pthread_t tid[4]; 
  52.     pthread_create(&tid[0],NULL,fcn1,NULL); 
  53.     pthread_create(&tid[1],NULL,fcn2,NULL); 
  54.     pause();     
  55.     return 0; 
  56.  





本文作者:佚名
来源:51CTO
目录
相关文章
|
1天前
|
Unix Linux 调度
linux线程与进程的区别及线程的优势
linux线程与进程的区别及线程的优势
|
1天前
|
Linux
linux中wait与waitpid函数使用场景及扩展
linux中wait与waitpid函数使用场景及扩展
|
2天前
|
存储 算法 Linux
【Linux】线程的内核级理解&&详谈页表以及虚拟地址到物理地址之间的转化
【Linux】线程的内核级理解&&详谈页表以及虚拟地址到物理地址之间的转化
|
5天前
|
存储 算法 网络协议
【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )
【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )
12 0
|
5天前
|
算法 Java Linux
【探索Linux】P.23(线程池 —— 简单模拟)
【探索Linux】P.23(线程池 —— 简单模拟)
9 0
|
5天前
|
存储 安全 Java
【探索Linux】P.21(多线程 | 线程同步 | 条件变量 | 线程安全)
【探索Linux】P.21(多线程 | 线程同步 | 条件变量 | 线程安全)
10 0
|
5天前
|
算法 安全 Linux
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
11 0
|
5天前
|
存储 安全 Linux
【探索Linux】P.19(多线程 | 线程的概念 | 线程控制 | 分离线程)
【探索Linux】P.19(多线程 | 线程的概念 | 线程控制 | 分离线程)
7 0