Linux下的多线程编程.pdf

所需积分/C币:24 2014-09-24 17:54:08 270KB PDF

Linux下的多线程编程.pdf 很好的一本书,值得看一看
This is a pthread This is a pthread 再次运行,我们可能得到如卜结果: This is a pthread This is the main process This is a pthread This is the main process. This is a pthread This is the main process 前后两次结果不一样,这是两个线程争夺CPL资源的结果。上面的示例中,我们使用到了两个函数, thread create和 pthread join,并声明了一个 pthread t型的变量。 pthread t在头文件/usr/ inc lude/bts/ pthreadtypes.h中定义: t t uni gned long int pthread t 它是一个线程的标识符。函数 pthread create用来创建一个线程,它的原型为: extern int pthread create P(pthread t thread, const pthread attr t attr, void *s start routine)(void *) void arg) 第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是 运行函数的参数。这旦,我们的函数 thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成 默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常 见的错误返回代码为 EAGAIN和 EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性 偵非法ε创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。 数 pthread join用米等待一个线稈的结束。函数原型为 extern int pthread join P((pthread t th, void sek thread return)) 第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个 线程阻塞的凶数,调用它的函数将一直等待到被等待的线程结束为止,当凶数返回时,被等待线程的资源被收回。一个线程的结束有 两种途径,一种是象我们上面的例」一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数 pthread exit来实现。它 的函数原型为 extern void pthread exit P ((void x retval)) attribute (( noreturn ) 唯一的参数是函数的返回代码,只要 pthread join中的第一个参数 thread return不是NUL,这个值将被传递给 thread return o 最后要说明的是,一仝线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用 pthread join的线程则返冋错误 代码 ESRCILO 在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数 pthread create, pthread join和 pthread exit。下面, 我们来了解线程的一些常用属性以及如何设置这些属性 3修改线程的属性 在上一节的例子里,我们用 pthread create函数创建了一个线程,在这个线程中,我们使用了∫默认参数,即将该函数的第二个参 数改为\UL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了一下线程的有关属性。 属性结构为 pthread attr t,它同样在头文件/usr/ include/ pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能 直接设置,须使用相关函数进行操作,初始化的函数为 pthread attr init,这个函数必须在 pthread create函数之前调用。属性对 象主要包括是否绑定、是否分离、堆栈地址、堆栈人小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别 的优先级。 关于线稈的绑定,牵涉到另外一个概念:轿进稈(LWP: Light Weight Process)。轿进稈可以理解为内核线稈,它位于用户层和 系统层之间。系统对线稈资源的分配、对线稈的抔制是通过轻进稈米实现的,一个轻进稈可以控制一个或多个线稈。默认状况下,启 动多少轻进程、哪些轻让程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固 定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CP时间片的调度是面向轻诖程的,绑定的线程可以保证 在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先纵和调度缴可以使得绑定的线程满足诸如实时反应之类的要求。 设置线程绑定状态的函数为 pthread attr setscope,它有两个参数,第-一个是指向属性结构的指针,第二个是绑定类型,它有两 个取值: PTHREAD SCOPE SYSTEM(绑定的)和 PTHREAD SCOPE PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。 include <pthread. h) pthread attr t attr pthread t tid; /初始化属性值,均设为默认值x pthread attr init(&attr pthrcad attr setscopc(&attr, PTHREAD SCOPE SYSTEM pthread create(&tid, &attr, (void *)my function, NULL 线程的分离状态决定一个线程以什么样的方式来终止白己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这 种情况下,原有的线稈等待创建的线程结束。只有当 pthread join()函数返回时,创建的线稈才算终止,才能释放自己占用的系统 资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该 根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为 pthread attr setdetachstate( pthread attr t来attr,int detachstate)。第二个参数可选为 PTHIREAD CREATE DETACHED(分离线程)和 PTIIREAD CREATE JOINABLE(非分离线程)。这里要注 意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread create函数返回之前就终止了,它终止 以后就可能将线程号和系统资源移交绐其他的线桯使用,这样调用υ threadεreaτε的线程就得到了错误的线程号。要避免这种情况可 以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread cond timewait函数,让这个线程等待一会儿,留 出足够的时间让函数 pthread create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类 的函数,它们是使整个进程睡眠,并不能解决线程同步的问题 另外一个可能常用的属性是线程的优先级,它存放在结构 sched param屮。用函数 pthread attr getschedparam和函数 thread attr- sctschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。 include pthread. h Include < sched. h> pthread attr t attr pthread t. tid sched param param int newprio-20 pthread attr init( &attr) pthread attr getschedparam( &attr, &param) param sched priority-newprio pthread attr set schedparam (&attr, &param) pthread create(&tid, &attr, ( void *)myfunction, myarg) 4线程的数据处理 和进稈相比,线稈的最大优点之一是数据的共享性,各个进稈共享父进稈处沿袭的缴据段,可以方便的获得、修改数据。但这也 给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数 的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的返冋值也会有问题。因为如果返回的是函数 内部静态声明的空间的地址,则在一·个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一 段数据。在进程中共享的变量必须用关键字 volatile来定义,这是为了防止编译器在优化时(如g·中使用-OX参数)改变它们的使 用方式ε为了保护变量,我们必须使用信号量、互斥等方法釆保证我们对变量的正确使用。下面,我们就逐步介绍处理线稈数据时的 有关知识 4.1线程数据 在单线程的程序里,有两种基夲的数据:全局交量和局部变量。但在多线程程序里,还有第种数据类型:线程数据(IS}) Thread- Specif' ic Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程 是不可见的。这种数掂的必要性是显而易见的。例如我们常见的变量 errno,它返回标准的出错信息。它显然个能是一个局部变量,儿 乎每个函数都应该可以调用它;但它又不能是一个全同变量,♂则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的 变量,我们就必须使用线程效据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都伩用这个键来指代线程数 据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容 和线程数据相关的函数主要冇4个:创建一个键:为一个键指定线程数捱;从一个键该取线程数据;删除键。 创建键的函数原型为: extern int pthread key create P ((pthrcad key t *k key void('c destr function)(void s))) 第一个参数为指回一个键值的指针,第二个参数指明了一个 destructor函数,如果这个参数不为空,那么当每个线稈结束时,系 统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数 pthread once(( pthread once t* once contro l,void (* initroutire)(void)一起使用,为了让这个键只被创建一次。函数 pthread once声明一个初始化函数,第一次调用 pthread once 时它执行这个函数,以后的调用将被它忽咯。 在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数 createwindow,这个函数定义一个图形窗口 (数据类型为 FI Window*,这是图形界面开发工具FT中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。 /*声明一个键* pthread key t my WinKey 米函数 create Window void createWindow( void) Fl Window s win static pthread once t once= PTHREAD ONCE INIT: /米调用函数 createMykey,创建键*/ pthread once(& once, createMy Key /*in指向一个新建立的窗口 win=new F1 Window(0. 0, 100, 100, MyWindow /*对此窗凵作一拦可能的设置工作,如大小、位置、名称等 set Window(win) /米将窗口指针值绑定在键 my WinKey上* pthread setpecific( my WinKey, win 函数 createMyKey,创建一个键,并指定 destructor* void createMyKey( void)i pthread keycreate( &my Winkey, freeWinKey) 米函数 freewinkey,释放空间* void freeWinKey( Fl Window x win delete win 这样,在不同的线程屮调用函数 crcateMy Win,都可以得到在线程内部均可见的窗口变量,这个变量通过函数 pthrcad getspccific 得到。在上面的例子屮,我们已经使用了函数 pthread setspccific来将线程数据和一个键绑定在一起。这两个函数的原型如下: extern int pthread setspecific p((pthread key t key const void ss pointer)) extern void *pthread get specific p((pthread key t key)) 这两个函数的参数意义和使用方法是显而易见的。要注意的是,用 pthread set specific为一个键指定新的线程数据时,必须自 匚释放原有的线程数据以回收空间。这个过程函数 pthread key delete用来刪除个键,这个键占用的内存将被释放,但同样要注意 的是,它只祥放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数 pthread key create中定义 的 destructor函数。线程数据的祥放必须在释放键之前完成。 4.2互斥锁 互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得 到的结果一定是灾难性的 我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有 两个状态:有信息或没有信息。 void reader function void void writer function void char buffer int buffer has item=o pthread mutex t mutex struct timespec delay void main( void)i 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 ite==0) buffer-make new item() buffer has item=1 /*打开互斥锁*/ pthread mutex unlock(&mutex) pthread delay np(&delay) void reader func tion(void)[ while( pthread mutex lock(&mutex) if (buffer has item==1)i consume item(buffer) buffer has item-0 pthread mutex unlock(&mutex pthread delay np &delay) 这里声明了互斥锁变量 mutex,结构 pthread mutex t为不公开的数据类型,其巾包含一个系统分配的属性对象。函数 pthread mutex init用来生成一个互斥锁,NU!参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数 pthread mutexattr init。函数 pthread mutexattr setpshared和函数 pthread mutexattr settype用末设置l斥锁属性。前一个函 数设置属性 shared,它有两个取值, PTIREAD 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 de lay np函数,让线程睡眠一段时间,就是为了防止一个线程始终 口据此函数。 上面的例子非常简单,就不再介绍了,需要提出的是在使用可斥锁的过稈中很有可能会出现死锁:两个线稈试图同时占用两个资 源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁 这时就出现了死锁。此时我们可以使用函数 pthread mutex try lock,它是函数 pthread mutex lock的非阻塞版本,当它发玩死锁不 可避免时,它会返回相应的信息,程序员可以针对邠锁散岀相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还 是要稈序员自己在稈序设计注意这一点。 4.3条件变量 前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两和状态:锁定和非锁定。 而条件变量通过允许线程阻塞和等待兄一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被 用来阻塞一个线程,当条件不满足时,线程往往解川相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将 通知相应的条件变量唤陧一个或多个正被此条件变量阻塞的线程。这些线程将車新锁定互斥锁并亘新测试条件是合淸足。一般说来, 条件变量被用来进行线间的同步 条作变量的结构为 pthread cond t,函数 pthread cond init()被用来初始化一个条件变量。它的原型为: extern int pthread cond init p((pthread cond t cond, const pthread condattr t cond attr) 其中cond是一个指向结构 pthread cond t的指针, cond attr是一个指向结构 pthread condattr t的指针。结构 pthread condattr t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是 PTHREAD PR0 CESS PRIVATE,即此条件变量被同一进程內的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被 释放。释放一个条什变量的函数为 pthread cond destroy( pthread cond t cond)。 函数 pthread cond wait()使线稈阻塞在一个条什变量上。它的函数原型为: extern int pthread cond wait p((pthread cond t x cond pthread multex t mutex) 线程解川mtex指向的锁并被条件变量cond阻塞。线程可以被函数 pthread cond signal和函数 pthread cond broadcast唤醒, 但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是含为0等等,这一点我们 从后面的例子中可以看到。线程被唤酲后,它将重新检査判凘条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等 待被下一次唤酲。这个过程一般用 while语句实现。 另一个用来陧塞线程的函数是 pthread cond timedwait(),它的原型为: extern int pthread cond timedwait p((pthread cond t cond pthread mutex t *k mutex, const struct timespec * k abstime) 它比函数 pthrcad cond wait()多了一个时间参数,经历ε betim段时间后,即使条件变量不满足,阻寒也被解除。 数 pthread cond signal()的原型为: extern int pthread cond signal P((pthread cond t *k cond)) 它用来释放被阻塞在条件交量cand上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决 定的。要注意的是,必须用保扩条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用 pthread cond wait 函数之间被发出,从而造成无限制的等待。下面是使用函数 pthread cond wait()和函数 pthread cond signa1()的个简单的例

...展开详情
试读 29P Linux下的多线程编程.pdf
img
mz454619501

关注 私信 TA的资源

上传资源赚积分,得勋章
    最新推荐
    Linux下的多线程编程.pdf 24积分/C币 立即下载
    1/29
    Linux下的多线程编程.pdf第1页
    Linux下的多线程编程.pdf第2页
    Linux下的多线程编程.pdf第3页
    Linux下的多线程编程.pdf第4页
    Linux下的多线程编程.pdf第5页
    Linux下的多线程编程.pdf第6页
    Linux下的多线程编程.pdf第7页
    Linux下的多线程编程.pdf第8页
    Linux下的多线程编程.pdf第9页

    试读已结束,剩余20页未读...

    24积分/C币 立即下载 >