免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1149 | 回复: 0
打印 上一主题 下一主题

Linux系统下的多线程编程入门三 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-08-26 10:56 |只看该作者 |倒序浏览

  线程的数据处理
  和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址,则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。下面,我们就逐步介绍处理线程数据时的有关知识。
  1、线程数据
  在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific
Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在
A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
  和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。
  创建键的函数原型为:
extern int pthread_key_create __P ((pthread_key_t *__key,void
(*__destr_function) (void *)));
  第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once
((pthread_once_t*once_control, void (*initroutine)
(void)))一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。
  在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数
createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window
*,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。
/* 声明一个键*/
pthread_key_t myWinKey;
/* 函数 createWindow
*/
void createWindow ( void ) {
 Fl_Window * win;
 static
pthread_once_t once= PTHREAD_ONCE_INIT;
 /*
调用函数createMyKey,创建键*/
 pthread_once ( & once, createMyKey)
;
 /*win指向一个新建立的窗口*/
 win=new Fl_Window( 0, 0, 100, 100,
"MyWindow");
 /* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/
 setWindow(win);
 /*
将窗口指针值绑定在键myWinKey上*/
 pthread_setpecific ( myWinKey, win);
}
/* 函数
createMyKey,创建一个键,并指定了destructor */
void createMyKey ( void )
{
 pthread_keycreate(&myWinKey, freeWinKey);
}
/* 函数
freeWinKey,释放空间*/
void freeWinKey ( Fl_Window * win){
 delete
win;
}
  这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数
pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:
  extern int pthread_setspecific __P ((pthread_key_t __key,__const
void *__pointer));
  extern void *pthread_getspecific __P ((pthread_key_t
__key));
  这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。
  2、互斥锁
  互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
  我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。
void reader_function ( void );
void writer_function ( void
);
char buffer;
int buffer_has_item=0;
pthread_mutex_t
mutex;
struct timespec delay;
void main ( void ){
 pthread_t
reader;
 /* 定义延迟时间*/
 delay.tv_sec = 2;
 delay.tv_nec = 0;
 /*
用默认属性初始化一个互斥锁对象*/
 pthread_mutex_init
(&mutex,NULL);
 pthread_create(&reader, pthread_attr_default, (void
*)&reader_function), NULL);
 writer_function( );
}
void
writer_function (void){
 while(1){
  /* 锁定互斥锁*/
  pthread_mutex_lock
(&mutex);
  if (buffer_has_item==0){
   buffer=make_new_item(
);
   buffer_has_item=1;
  }
  /*
打开互斥锁*/
  pthread_mutex_unlock(&mutex);
  pthread_delay_np(&delay);
 }
}
void
reader_function(void){
 while(1){
  pthread_mutex_lock(&mutex);
  if(buffer_has_item==1){
   consume_item(buffer);
   buffer_has_item=0;
  }
  pthread_mutex_unlock(&mutex);
  pthread_delay_np(&delay);
 }
}
  这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数
pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数
pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数
pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,
PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_
PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、
PTHREAD_MUTEX_RECURSIVE和PTHREAD
_MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。
  
pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。
  上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数
pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/54431/showart_1148839.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP