多线程编程:UNIXLinux下的多线程编程来源: 发布时间:星期四, 2008年12月11日 浏览:7次 评论:0
Linux下多线程编程
80nfbaiducukd7O本文出自:http://www.china-pub.com 作者: 姚继锋 (2001-08-11 09:05:00) 80nfbaiducukd7O1 引言 80nfbaiducukd7O 线程(thread)技术早在60年代就被提出但真正应用多线程到操作系统中去是在80年代中期solaris是这方面佼佼者传统Unix也支持线程概念但是在个进程(process)中只允许有个线程这样多线程就意味着多进程现在多线程技术已经被许多操作系统所支持包括Windows/NT当然也包括Linux 80nfbaiducukd7O 为什么有了进程概念后还要再引入线程呢?使用多线程到底有哪些好处?什么系统应该选用多线程?我们首先必须回答这些问题 80nfbaiducukd7O 使用多线程理由之是和进程相比它是种非常"节俭"多任务操作方式我们知道在Linux系统下启动个新进程必须分配给它独立地址空间建立众多数据表来维护它代码段、堆栈段和数据段这是种"昂贵"多任务工作方式而运行于个进程中多个线程它们彼此之间使用相同地址空间共享大部分数据启动个线程所花费空间远远小于启动个进程所花费空间而且线程间彼此切换所需时间也远远小于进程间切换所需要时间据统计总说来个进程开销大约是个线程开销30倍左右当然在具体系统上这个数据可能会有较大区别 80nfbaiducukd7O 使用多线程理由之二是线程间方便通信机制对不同进程来说它们具有独立数据空间要进行数据传递只能通过通信方式进行这种方式不仅费时而且很不方便线程则不然由于同进程下线程之间共享数据空间所以个线程数据可以直接为其它线程所用这不仅快捷而且方便当然数据共享也带来其他些问题有变量不能同时被两个线程所修改有子中声明为数据更有可能给多线程带来灾难性打击这些正是编写多线程时最需要注意地方 80nfbaiducukd7O 除了以上所说优点外不和进程比较多线程作为种多任务、并发工作方式当然有以下优点: 80nfbaiducukd7O 1) 提高应用响应这对图形界面尤其有意义当个操作耗时很长时整个系统都会等待这个操作此时不会响应键盘、鼠标、菜单操作而使用多线程技术将耗时长操作(time consuming)置于个新线程可以避免这种尴尬情况 80nfbaiducukd7O 2) 使多CPU系统更加有效操作系统会保证当线程数不大于CPU数目时不同线程运行于不同CPU上 80nfbaiducukd7O 3) 改善结构个既长又复杂进程可以考虑分为多个线程成为几个独立或半独立运行部分这样会利于理解和修改 80nfbaiducukd7O 下面我们先来尝试编写个简单多线程 80nfbaiducukd7O 80nfbaiducukd7O2 简单多线程编程 80nfbaiducukd7O Linux系统下多线程遵循POSIX线程接口称为pthread编写Linux下多线程需要使用头文件pthread.h连接时需要使用库libpthread.a顺便说下Linux下pthread实现是通过系统clone()来实现clone()是Linux所特有系统它使用方式类似fork关于clone()详细情况有兴趣读者可以去查看有关文档说明下面我们展示个最简单多线程example1.c 80nfbaiducukd7O 80nfbaiducukd7O/* example.c*/ 80nfbaiducukd7O# <stdio.h> 80nfbaiducukd7O# <pthread.h> 80nfbaiducukd7Ovoid thread(void) 80nfbaiducukd7O{ 80nfbaiducukd7O i; 80nfbaiducukd7Ofor(i=0;i<3;i) 80nfbaiducukd7Oprf("This is a pthread.\n"); 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7O (void) 80nfbaiducukd7O{ 80nfbaiducukd7Opthread_t id; 80nfbaiducukd7O i,ret; 80nfbaiducukd7Oret=pthread_create(&id,NULL,(void *) thread,NULL); 80nfbaiducukd7O(ret!=0){ 80nfbaiducukd7Oprf ("Create pthread error!\n"); 80nfbaiducukd7Oexit (1); 80nfbaiducukd7O} 80nfbaiducukd7Ofor(i=0;i<3;i) 80nfbaiducukd7Oprf("This is the process.\n"); 80nfbaiducukd7Opthread_join(id,NULL); 80nfbaiducukd7O (0); 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7O我们编译此: 80nfbaiducukd7Ogcc example1.c -lpthread -o example1 80nfbaiducukd7O运行example1我们得到如下结果: 80nfbaiducukd7OThis is the process. 80nfbaiducukd7OThis is a pthread. 80nfbaiducukd7OThis is the process. 80nfbaiducukd7OThis is the process. 80nfbaiducukd7OThis is a pthread. 80nfbaiducukd7OThis is a pthread. 80nfbaiducukd7O再次运行我们可能得到如下结果: 80nfbaiducukd7OThis is a pthread. 80nfbaiducukd7OThis is the process. 80nfbaiducukd7OThis is a pthread. 80nfbaiducukd7OThis is the process. 80nfbaiducukd7OThis is a pthread. 80nfbaiducukd7OThis is the process. 80nfbaiducukd7O 80nfbaiducukd7O 前后两次结果不样这是两个线程争夺CPU资源结果上面示例中我们使用到了两个 pthread_create和pthread_join并声明了个pthread_t型变量 80nfbaiducukd7O pthread_t在头文件/usr//bits/pthreadtypes.h中定义: 80nfbaiducukd7O typedef unsigned long pthread_t; 80nfbaiducukd7O 它是个线程标识符pthread_create用来创建个线程它原型为: 80nfbaiducukd7O extern pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr, 80nfbaiducukd7O void *(*__start_routine) (void *), void *__arg)); 80nfbaiducukd7O 第个参数为指向线程标识符指针第二个参数用来设置线程属性第三个参数是线程运行起始地址最后个参数是运行参数这里我们thread不需要参数所以最后个参数设为空指针第二个参数我们也设为空指针这样将生成默认属性线程对线程属性设定和修改我们将在下节阐述当创建线程成功时返回0若不为0则说明创建线程失败常见返回代码为EAGAIN和EINVAL前者表示系统限制创建新线程例如线程数目过多了;后者表示第二个参数代表线程属性值非法创建线程成功后新创建线程则运行参数三和参数四确定原来线程则继续运行下行代码 80nfbaiducukd7O pthread_join用来等待个线程结束原型为: 80nfbaiducukd7O extern pthread_join __P ((pthread_t __th, void **__thread_)); 80nfbaiducukd7O 第个参数为被等待线程标识符第二个参数为个用户定义指针它可以用来存储被等待线程返回值这个是个线程阻塞它将直等待到被等待线程结束为止当返回时被等待线程资源被收回个线程结束有两种途径种是象我们上面例子样结束了它线程也就结束了;另种方式是通过pthread_exit来实现它原型为: 80nfbaiducukd7O extern void pthread_exit __P ((void *__retval)) __attribute__ ((__no__)); 80nfbaiducukd7O 唯参数是返回代码只要pthread_join中第二个参数thread_不是NULL这个值将被传递给thread_最后要说明是个线程不能被多个线程等待否则第个接收到信号线程成功返回其余pthread_join线程则返回代码ESRCH 80nfbaiducukd7O 在这节里我们编写了个最简单线程并掌握了最常用三个pthread_createpthread_join和pthread_exit下面我们来了解线程些常用属性以及如何设置这些属性 80nfbaiducukd7O 80nfbaiducukd7O3 修改线程属性 80nfbaiducukd7O 在上节例子里我们用pthread_create创建了个线程在这个线程中我们使用了默认参数即将该第二个参数设为NULL确对大多数来说使用默认属性就够了但我们还是有必要来了解下线程有关属性 80nfbaiducukd7O 属性结构为pthread_attr_t它同样在头文件/usr//pthread.h中定义喜欢追根问底人可以自己去查看属性值不能直接设置须使用相关进行操作化为pthread_attr_init这个必须在pthread_create之前属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级默认属性为非绑定、非分离、缺省1M堆栈、与父进程同样级别优先级 80nfbaiducukd7O 关于线程绑定牵涉到另外个概念:轻进程(LWP:Light Weight Process)轻进程可以理解为内核线程它位于用户层和系统层之间系统对线程资源分配、对线程控制是通过轻进程来实现个轻进程可以控制个或多个线程默认状况下启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制这种状况即称为非绑定绑定状况下则顾名思义即某个线程固定"绑"在个轻进程之上被绑定线程具有较高响应速度这是CPU时间片调度是面向轻进程绑定线程可以保证在需要时候它总有个轻进程可用通过设置被绑定轻进程优先级和调度级可以使得绑定线程满足诸如实时反应之类要求 80nfbaiducukd7O 设置线程绑定状态为pthread_attr_scope它有两个参数第个是指向属性结构指针第二个是绑定类型它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定)和PTHREAD_SCOPE_PROCESS(非绑定)下面代码即创建了个绑定线程 80nfbaiducukd7O# <pthread.h> 80nfbaiducukd7Opthread_attr_t attr; 80nfbaiducukd7Opthread_t tid; 80nfbaiducukd7O 80nfbaiducukd7O/*化属性值均设为默认值*/ 80nfbaiducukd7Opthread_attr_init(&attr); 80nfbaiducukd7Opthread_attr_scope(&attr, PTHREAD_SCOPE_SYSTEM); 80nfbaiducukd7O 80nfbaiducukd7Opthread_create(&tid, &attr, (void *) my_function, NULL); 80nfbaiducukd7O 80nfbaiducukd7O 线程分离状态决定个线程以什么样方式来终止自己在上面例子中我们采用了线程默认属性即为非分离状态这种情况下原有线程等待创建线程结束只有当pthread_join()返回时创建线程才算终止才能释放自己占用系统资源而分离线程不是这样子它没有被其他线程所等待自己运行结束了线程也就终止了马上释放系统资源员应该根据自己需要选择适当分离状态设置线程分离状态为pthread_attr_detachstate(pthread_attr_t *attr, detachstate)第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)这里要注意点是如果设置个线程为分离线程而这个线程运行又非常快它很可能在pthread_create返回之前就终止了它终止以后就可能将线程号和系统资源移交给其他线程使用这样pthread_create线程就得到了线程号要避免这种情况可以采取定同步措施最简单方法之是可以在被创建线程里pthread_cond_timewait让这个线程等待会儿留出足够时间让pthread_create返回设置段等待时间是在多线程编程里常用方法但是注意不要使用诸如wait()之类它们是使整个进程睡眠并不能解决线程同步问题 80nfbaiducukd7O 另外个可能常用属性是线程优先级它存放在结构sched_param中用pthread_attr_getschedparam和pthread_attr_schedparam进行存放般说来我们总是先取优先级对取得值修改后再存放回去下面即是段简单例子 80nfbaiducukd7O# <pthread.h> 80nfbaiducukd7O# <sched.h> 80nfbaiducukd7Opthread_attr_t attr; 80nfbaiducukd7Opthread_t tid; 80nfbaiducukd7Osched_param param; 80nfbaiducukd7O prio=20; 80nfbaiducukd7O 80nfbaiducukd7Opthread_attr_init(&attr); 80nfbaiducukd7Opthread_attr_getschedparam(&attr, ¶m); 80nfbaiducukd7Oparam.sched_priority=prio; 80nfbaiducukd7Opthread_attr_schedparam(&attr, ¶m); 80nfbaiducukd7Opthread_create(&tid, &attr, (void *)myfunction, myarg); 80nfbaiducukd7O 80nfbaiducukd7O4 线程数据处理 80nfbaiducukd7O 和进程相比线程最大优点之是数据共享性各个进程共享父进程处沿袭数据段可以方便获得、修改数据但这也给多线程编程带来了许多问题我们必须当心有多个不同进程访问相同变量许多是不可重入即同时不能运行个多个拷贝(除非使用不同数据段)在中声明静态变量常常带来问题返回值也会有问题如果返回是内部静态声明空间地址则在个线程该得到地址后使用该地址指向数据时别线程可能此并修改了这段数据在进程中共享变量必须用关键字volatile来定义这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们使用方式为了保护变量我们必须使用信号量、互斥等方法来保证我们对变量正确使用下面我们就逐步介绍处理线程数据时有关知识 80nfbaiducukd7O 80nfbaiducukd7O4.1 线程数据 80nfbaiducukd7O 在单线程里有两种基本数据:全局变量和局部变量但在多线程里还有第三种数据类型:线程数据(TSD: Thread-Specic Data)它和全局变量很象在线程内部各个可以象使用全局变量样它但它对线程外部其它线程是不可见这种数据必要性是显而易见例如我们常见变量errno它返回标准出错信息它显然不能是个局部变量几乎每个都应该可以它;但它又不能是个全局变量否则在A线程里输出很可能是B线程出错信息要实现诸如此类变量我们就必须使用线程数据我们为每个线程数据创建个键它和这个键相关联在各个线程里都使用这个键来指代线程数据但在不同线程里这个键代表数据是不同在同个线程里它代表同样数据内容 80nfbaiducukd7O 和线程数据相关主要有4个:创建个键;为个键指定线程数据;从个键读取线程数据;删除键 80nfbaiducukd7O 创建键原型为: 80nfbaiducukd7O extern pthread_key_create __P ((pthread_key_t *__key, 80nfbaiducukd7O void (*__destr_function) (void *))); 80nfbaiducukd7O 第个参数为指向个键值指针第二个参数指明了个destructor如果这个参数不为空那么当每个线程结束时系统将这个来释放绑定在这个键上内存块这个常和pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))起使用为了让这个键只被创建次pthread_once声明个化第次pthread_once时它执行这个以后将被它忽略 80nfbaiducukd7O 80nfbaiducukd7O 在下面例子中我们创建个键并将它和某个数据相关联我们要定义个createWindow这个定义个图形窗口(数据类型为Fl_Window *这是图形界面开发工具FLTK中数据类型)由于各个线程都会这个所以我们使用线程数据 80nfbaiducukd7O/* 声明个键*/ 80nfbaiducukd7Opthread_key_t myWinKey; 80nfbaiducukd7O/* createWindow */ 80nfbaiducukd7Ovoid createWindow ( void ) { 80nfbaiducukd7OFl_Window * win; 80nfbaiducukd7O pthread_once_t once= PTHREAD_ONCE_INIT; 80nfbaiducukd7O/* createMyKey创建键*/ 80nfbaiducukd7Opthread_once ( & once, createMyKey) 80nfbaiducukd7O/*win指向个新建立窗口*/ 80nfbaiducukd7Owin= Fl_Window( 0, 0, 100, 100, "MyWindow"); 80nfbaiducukd7O/* 对此窗口作些可能设置工作如大小、位置、名称等*/ 80nfbaiducukd7OWindow(win); 80nfbaiducukd7O/* 将窗口指针值绑定在键myWinKey上*/ 80nfbaiducukd7Opthread_pecic ( myWinKey, win); 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7O/* createMyKey创建个键并指定了destructor */ 80nfbaiducukd7Ovoid createMyKey ( void ) { 80nfbaiducukd7Opthread_keycreate(&myWinKey, freeWinKey); 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7O/* freeWinKey释放空间*/ 80nfbaiducukd7Ovoid freeWinKey ( Fl_Window * win){ 80nfbaiducukd7Odelete win; 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7O 这样在不同线程中createMyWin都可以得到在线程内部均可见窗口变量这个变量通过pthread_getspecic得到在上面例子中我们已经使用了pthread_specic来将线程数据和个键绑定在起这两个原型如下: 80nfbaiducukd7O extern pthread_specic __P ((pthread_key_t __key,__const void *__poer)); 80nfbaiducukd7O extern void *pthread_getspecic __P ((pthread_key_t __key)); 80nfbaiducukd7O 这两个参数意义和使用方法是显而易见要注意是用pthread_specic为个键指定新线程数据时必须自己释放原有线程数据以回收空间这个过程pthread_key_delete用来删除个键这个键占用内存将被释放但同样要注意是它只释放键占用内存并不释放该键关联线程数据所占用内存资源而且它也不会触发pthread_key_create中定义destructor线程数据释放必须在释放键之前完成 80nfbaiducukd7O 80nfbaiducukd7O4.2 互斥锁 80nfbaiducukd7O 互斥锁用来保证段时间内只有个线程在执行段代码必要性显而易见:假设各个线程向同个文件顺序写入数据最后得到结果定是灾难性 80nfbaiducukd7O 我们先看下面段代码这是个读/写它们公用个缓冲区并且我们假定个缓冲区只能保存条信息即缓冲区只有两个状态:有信息或没有信息 80nfbaiducukd7O 80nfbaiducukd7Ovoid reader_function ( void ); 80nfbaiducukd7Ovoid writer_function ( void ); 80nfbaiducukd7O 80nfbaiducukd7Ochar buffer; 80nfbaiducukd7O buffer_has_item=0; 80nfbaiducukd7Opthread_mutex_t mutex; 80nfbaiducukd7Ostruct timespec delay; 80nfbaiducukd7Ovoid ( void ){ 80nfbaiducukd7Opthread_t reader; 80nfbaiducukd7O/* 定义延迟时间*/ 80nfbaiducukd7Odelay.tv_sec = 2; 80nfbaiducukd7Odelay.tv_nec = 0; 80nfbaiducukd7O/* 用默认属性化个互斥锁对象*/ 80nfbaiducukd7Opthread_mutex_init (&mutex,NULL); 80nfbaiducukd7Opthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL); 80nfbaiducukd7Owriter_function( ); 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7Ovoid writer_function (void){ 80nfbaiducukd7Owhile(1){ 80nfbaiducukd7O/* 锁定互斥锁*/ 80nfbaiducukd7Opthread_mutex_lock (&mutex); 80nfbaiducukd7O (buffer_has_item0){ 80nfbaiducukd7Obuffer=make__item( ); 80nfbaiducukd7Obuffer_has_item=1; 80nfbaiducukd7O} 80nfbaiducukd7O/* 打开互斥锁*/ 80nfbaiducukd7Opthread_mutex_unlock(&mutex); 80nfbaiducukd7Opthread_delay_np(&delay); 80nfbaiducukd7O} 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7Ovoid reader_function(void){ 80nfbaiducukd7Owhile(1){ 80nfbaiducukd7Opthread_mutex_lock(&mutex); 80nfbaiducukd7O(buffer_has_item1){ 80nfbaiducukd7Oconsume_item(buffer); 80nfbaiducukd7Obuffer_has_item=0; 80nfbaiducukd7O} 80nfbaiducukd7Opthread_mutex_unlock(&mutex); 80nfbaiducukd7Opthread_delay_np(&delay); 80nfbaiducukd7O} 80nfbaiducukd7O} 80nfbaiducukd7O 这里声明了互斥锁变量mutex结构pthread_mutex_t为不公开数据类型其中包含个系统分配属性对象pthread_mutex_init用来生成个互斥锁NULL参数表明使用默认属性如果需要声明特定属性互斥锁须pthread_mutexattr_initpthread_mutexattr_pshared和pthread_mutexattr_type用来设置互斥锁属性前个设置属性pshared它有两个取值PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED前者用来不同进程中线程同步后者用于同步本进程不同线程在上面例子中我们使用是默认属性PTHREAD_PROCESS_ PRIVATE后者用来设置互斥锁类型可选类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT它们分别定义了不同上所、解锁机制般情况下选用最后个默认属性 80nfbaiducukd7O pthread_mutex_lock声明开始用互斥锁上锁此后代码直至pthread_mutex_unlock为止均被上锁即同时间只能被个线程执行当个线程执行到pthread_mutex_lock处时如果该锁此时被另个线程使用那此线程被阻塞即将等待到另个线程释放此互斥锁在上面例子中我们使用了pthread_delay_np让线程睡眠段时间就是为了防止个线程始终占据此 80nfbaiducukd7O 上面例子非常简单就不再介绍了需要提出是在使用互斥锁过程中很有可能会出现死锁:两个线程试图同时占用两个资源并按不同次序锁定相应互斥锁例如两个线程都需要锁定互斥锁1和互斥锁2a线程先锁定互斥锁1b线程先锁定互斥锁2这时就出现了死锁此时我们可以使用pthread_mutex_trylock它是pthread_mutex_lock非阻塞版本当它发现死锁不可避免时它会返回相应信息员可以针对死锁做出相应处理另外不同互斥锁类型对死锁处理不样但最主要还是要员自己在设计注意这点 80nfbaiducukd7O 80nfbaiducukd7O4.3 条件变量 80nfbaiducukd7O 前节中我们讲述了如何使用互斥锁来实现线程间数据共享和通信互斥锁个明显缺点是它只有两种状态:锁定和非锁定而条件变量通过允许线程阻塞和等待另个线程发送信号方法弥补了互斥锁不足它常和互斥锁起使用使用时条件变量被用来阻塞个线程当条件不满足时线程往往解开相应互斥锁并等待条件发生变化旦其它某个线程改变了条件变量它将通知相应条件变量唤醒个或多个正被此条件变量阻塞线程这些线程将重新锁定互斥锁并重新测试条件是否满足般说来条件变量被用来进行线承间同步 80nfbaiducukd7O 条件变量结构为pthread_cond_tpthread_cond_init()被用来化个条件变量它原型为: 80nfbaiducukd7O extern pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr)); 80nfbaiducukd7O 其中cond是个指向结构pthread_cond_t指针cond_attr是个指向结构pthread_condattr_t指针结构pthread_condattr_t是条件变量属性结构和互斥锁样我们可以用它来设置条件变量是进程内可用还是进程间可用默认值是PTHREAD_ PROCESS_PRIVATE即此条件变量被同进程内各个线程使用注意化条件变量只有未被使用时才能重新化或被释放释放个条件变量为pthread_cond_ destroy(pthread_cond_t cond) 80nfbaiducukd7O pthread_cond_wait()使线程阻塞在个条件变量上它原型为: 80nfbaiducukd7O extern pthread_cond_wait __P ((pthread_cond_t *__cond, 80nfbaiducukd7O pthread_mutex_t *__mutex)); 80nfbaiducukd7O 线程解开mutex指向锁并被条件变量cond阻塞线程可以被pthread_cond_signal和pthread_cond_broadcast唤醒但是要注意是条件变量只是起阻塞和唤醒线程作用具体判断条件还需用户给出例如个变量是否为0等等这点我们从后面例子中可以看到线程被唤醒后它将重新检查判断条件是否满足如果还不满足般说来线程应该仍阻塞在这里被等待被下次唤醒这个过程般用while语句实现 80nfbaiducukd7O 另个用来阻塞线程是pthread_cond_timedwait()它原型为: 80nfbaiducukd7O extern pthread_cond_timedwait __P ((pthread_cond_t *__cond, 80nfbaiducukd7O pthread_mutex_t *__mutex, __const struct timespec *__abstime)); 80nfbaiducukd7O 它比pthread_cond_wait()多了个时间参数经历abstime段时间后即使条件变量不满足阻塞也被解除 80nfbaiducukd7O pthread_cond_signal()原型为: 80nfbaiducukd7O extern pthread_cond_signal __P ((pthread_cond_t *__cond)); 80nfbaiducukd7O 它用来释放被阻塞在条件变量cond上个线程多个线程阻塞在此条件变量上时哪个线程被唤醒是由线程调度策略所决定要注意是必须用保护条件变量互斥锁来保护这个否则条件满足信号又可能在测试条件和pthread_cond_wait之间被发出从而造成无限制等待下面是使用pthread_cond_wait()和pthread_cond_signal()个简单例子 80nfbaiducukd7O 80nfbaiducukd7Opthread_mutex_t count_lock; 80nfbaiducukd7Opthread_cond_t count_nonzero; 80nfbaiducukd7Ounsigned count; 80nfbaiducukd7Odecrement_count { 80nfbaiducukd7Opthread_mutex_lock (&count_lock); 80nfbaiducukd7Owhile(count0) 80nfbaiducukd7Opthread_cond_wait( &count_nonzero, &count_lock); 80nfbaiducukd7Ocount=count -1; 80nfbaiducukd7Opthread_mutex_unlock (&count_lock); 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7Oincrement_count{ 80nfbaiducukd7Opthread_mutex_lock(&count_lock); 80nfbaiducukd7O(count0) 80nfbaiducukd7Opthread_cond_signal(&count_nonzero); 80nfbaiducukd7Ocount=count+1; 80nfbaiducukd7Opthread_mutex_unlock(&count_lock); 80nfbaiducukd7O} 80nfbaiducukd7O count值为0时decrement在pthread_cond_wait处被阻塞并打开互斥锁count_lock此时当到increment_count时pthread_cond_signal()改变条件变量告知decrement_count()停止阻塞读者可以试着让两个线程分别运行这两个看看会出现什么样结果 80nfbaiducukd7O pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上线程这些线程被唤醒后将再次竞争相应互斥锁所以必须小心使用这个 80nfbaiducukd7O 80nfbaiducukd7O4.4 信号量 80nfbaiducukd7O 信号量本质上是个非负整数计数器它被用来控制对公共资源访问当公共资源增加时sem_post()增加信号量只有当信号量值大于0时才能使用公共资源使用后sem_wait()减少信号量sem_trywait()和pthread_ mutex_trylock()起同样作用它是sem_wait()非阻塞版本下面我们逐个介绍和信号量有关些它们都在头文件/usr//semaphore.h中定义 80nfbaiducukd7O 信号量数据类型为结构sem_t它本质上是个长整型数sem_init()用来化个信号量它原型为: 80nfbaiducukd7O extern sem_init __P ((sem_t *__sem, __pshared, unsigned __value)); 80nfbaiducukd7O sem为指向信号量结构个指针;pshared不为0时此信号量在进程间共享否则只能为当前进程所有线程共享;value给出了信号量值 80nfbaiducukd7O sem_post( sem_t *sem )用来增加信号量值当有线程阻塞在这个信号量上时这个会使其中个线程不在阻塞选择机制同样是由线程调度策略决定 80nfbaiducukd7O sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem值大于0解除阻塞后将sem值减表明公共资源经使用后减少sem_trywait ( sem_t *sem )是sem_wait()非阻塞版本它直接将信号量sem值减 80nfbaiducukd7O sem_destroy(sem_t *sem)用来释放信号量sem 80nfbaiducukd7O 下面我们来看个使用信号量例子在这个例子中共有4个线程其中两个线程负责从文件读取数据到公共缓冲区另两个线程从缓冲区读取数据作不同处理(加和乘运算) 80nfbaiducukd7O/* File sem.c */ 80nfbaiducukd7O# <stdio.h> 80nfbaiducukd7O# <pthread.h> 80nfbaiducukd7O# <semaphore.h> 80nfbaiducukd7O# MAXSTACK 100 80nfbaiducukd7O stack[MAXSTACK][2]; 80nfbaiducukd7O size=0; 80nfbaiducukd7Osem_t sem; 80nfbaiducukd7O/* 从文件1.dat读取数据每读次信号量加*/ 80nfbaiducukd7Ovoid ReadData1(void){ 80nfbaiducukd7OFILE *fp=fopen("1.dat","r"); 80nfbaiducukd7Owhile(!feof(fp)){ 80nfbaiducukd7Ofscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); 80nfbaiducukd7Osem_post(&sem); 80nfbaiducukd7Osize; 80nfbaiducukd7O} 80nfbaiducukd7Ofclose(fp); 80nfbaiducukd7O} 80nfbaiducukd7O/*从文件2.dat读取数据*/ 80nfbaiducukd7Ovoid ReadData2(void){ 80nfbaiducukd7OFILE *fp=fopen("2.dat","r"); 80nfbaiducukd7Owhile(!feof(fp)){ 80nfbaiducukd7Ofscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); 80nfbaiducukd7Osem_post(&sem); 80nfbaiducukd7Osize; 80nfbaiducukd7O} 80nfbaiducukd7Ofclose(fp); 80nfbaiducukd7O} 80nfbaiducukd7O/*阻塞等待缓冲区有数据读取数据后释放空间继续等待*/ 80nfbaiducukd7Ovoid HandleData1(void){ 80nfbaiducukd7Owhile(1){ 80nfbaiducukd7Osem_wait(&sem); 80nfbaiducukd7Oprf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1], 80nfbaiducukd7Ostack[size][0]+stack[size][1]); 80nfbaiducukd7O--size; 80nfbaiducukd7O} 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7Ovoid HandleData2(void){ 80nfbaiducukd7Owhile(1){ 80nfbaiducukd7Osem_wait(&sem); 80nfbaiducukd7Oprf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1], 80nfbaiducukd7Ostack[size][0]*stack[size][1]); 80nfbaiducukd7O--size; 80nfbaiducukd7O} 80nfbaiducukd7O} 80nfbaiducukd7O (void){ 80nfbaiducukd7Opthread_t t1,t2,t3,t4; 80nfbaiducukd7Osem_init(&sem,0,0); 80nfbaiducukd7Opthread_create(&t1,NULL,(void *)HandleData1,NULL); 80nfbaiducukd7Opthread_create(&t2,NULL,(void *)HandleData2,NULL); 80nfbaiducukd7Opthread_create(&t3,NULL,(void *)ReadData1,NULL); 80nfbaiducukd7Opthread_create(&t4,NULL,(void *)ReadData2,NULL); 80nfbaiducukd7O/* 防止过早退出让它在此无限期等待*/ 80nfbaiducukd7Opthread_join(t1,NULL); 80nfbaiducukd7O} 80nfbaiducukd7O 80nfbaiducukd7O 在Linux下我们用命令gcc -lpthread sem.c -o sem生成可执行文件sem 我们事先编辑好数据文件1.dat和2.dat假设它们内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 我们运行sem得到如下结果: 80nfbaiducukd7OMultiply:-1*-2=2 80nfbaiducukd7OPlus:-1+-2=-3 80nfbaiducukd7OMultiply:9*10=90 80nfbaiducukd7OPlus:-9+-10=-19 80nfbaiducukd7OMultiply:-7*-8=56 80nfbaiducukd7OPlus:-5+-6=-11 80nfbaiducukd7OMultiply:-3*-4=12 80nfbaiducukd7OPlus:9+10=19 80nfbaiducukd7OPlus:7+8=15 80nfbaiducukd7OPlus:5+6=11 80nfbaiducukd7O 80nfbaiducukd7O 从中我们可以看出各个线程间竞争关系而数值并未按我们原先顺序显示出来这是由于size这个数值被各个线程任意修改缘故这也往往是多线程编程要注意问题 80nfbaiducukd7O 80nfbaiducukd7O5 小结 80nfbaiducukd7O 多线程编程是个很有意思也很有用技术使用多线程技术网络蚂蚁是目前最常用下载工具之使用多线程技术grep比单线程grep要快上几倍类似例子还有很多希望大家能用多线程技术写出高效实用好来 80nfbaiducukd7O 80nfbaiducukd7O 80nfbaiducukd7O(http://www.fanqiang.com/) 进入【UNIX论坛】 80nfbaiducukd7O 80nfbaiducukd7O 0
相关文章
读者评论
发表评论 |