




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
BlaiseBarney,LawrenceLivermoreNationalLaboratory目录表摘要Pthreads概述什么是线程?什么是Pthreads?为什么使用Pthreads?使用线程设计程序"PthreadsAPI编译多线程程序线程管理创建和终止线程向线程传递参数连接(Joining)和分离(Detaching)线程栈管理其它函数互斥量(MutexVariables)互斥量概述创建和销毁互斥量锁定(Locking)和解锁(Unlocking)互斥量条件变量(ConditionVariable)条件变量概述创建和销毁条件变量等待(Waiting)和发送信号(Signaling)没有覆盖的主题Pthread库API参考参考资料在多处理器共享内存的架构中(如:对称多处理系统SMP),线程可以用于实现程序的并行性。历史上硬件销售商实现了各种私有版本的多线程库,使得软件开发者不得不关心它的移植性。对于UNIX系统,IEEEPOSIX1003.1标准定义了一个C语言多线程编程接口。依附于该标准的实现被称为POSIXtheads或Pthreads。该教程介绍了Pthreads的概念、动机和设计思想。内容包含了PthreadsAPI主要的三大类函数:线程管理(ThreadManagment)、互斥量(MutexVariables)和条件变量(ConditionVariables)。向刚开始学习Pthreads的程序员提供了演示例程。适于:刚开始学习使用线程实现并行程序设计;对于C并行程序设计有基本了解。不熟悉并行程序设计的可以参考EC3500:IntroductionToParallelComputingoPthreads概述什么是线程?技术上,线程可以定义为:可以被操作系统调度的独立的指令流。但是这是什么意思呢?对于软件开发者,在主程序中运行的“函数过程”可以很好的描述线程的概念。•进一步,想象下主程序(a.out)包含了许多函数,操作系统可以调度这些函数,使之同时或者(和)独立的执行。这就描述了“多线程”程序。怎样完成的呢?•在理解线程之前,应先对UNIX进程(process)有所了解。进程被操作系统创建,需要相当多的“额外开销”。进程包含了程序的资源和执行状态信息。如下:o进程ID,进程groupID,用户ID和groupIDo环境o工作目录o程序指令o寄存器o栈o堆o文件描述符o信号动作(Signalactions)o共享库o进程间通信工具(如:消息队列,管道,信号量或共享内存)
UserAddressSpaceUserAddressSpaceThread2dataheaproutlnelvad()g虫()Thread1Me/tfou11ne1柿varlwa.r2marnG匸口口tm*el()UserAddressSpaceUserAddressSpaceThread2dataheaproutlnelvad()g虫()Thread1Me/tfou11ne1柿varlwa.r2marnG匸口口tm*el()rant (}ProcessIIUserIDGroupIDStackPairPrgrm.CaRegEstersroutinQ2()va.rl\rar2varSStackPollPr^riri.CoRegistersmain()EQUt-l口亡丄cQutine2dataFilesLocksSocketsUNIXPROCESSTHREADSWITHINAUNIX
PROCESSUNIXPROCESS•线程使用并存在于进程资源中,还可以被操作系统调用并独立地运行,这主要是因为线程仅仅复制必要的资源以使自己得以存在并执行。•独立的控制流得以实现是因为线程维持着自己的:o堆栈指针o寄存器o调度属性(如:策略或优先级)o待定的和阻塞的信号集合(Setofpendingandblockedsignals)o线程专用数据(TSD:ThreadSpecificData.)•因此,在UNIX环境下线程:o存在于进程,使用进程资源o拥有自己独立的控制流,只要父进程存在并且操作系统支持o只复制必可以使得独立调度的必要资源o可以和其他线程独立(或非独立的)地共享进程资源o当父进程结束时结束,或者相关类似的o是“轻型的”,因为大部分额外开销已经在进程创建时完成了•因为在同一个进程中的线程共享资源:o一个线程对系统资源(如关闭一个文件)的改变对所有其它线程是可以见的o两个同样值的指针指向相同的数据o读写同一个内存位置是可能的,因此需要成员显式地使用同步Pthreads概述什么是Pthreads?•历史上,硬件销售商实现了私有版本的多线程库。这些实现在本质上各自不同,使得程序员难于开发可移植的应用程序。•为了使用线程所提供的强大优点,需要一个标准的程序接口。对于UNIX系统,IEEEPOSIX1003.1c(1995)标准制订了这一标准接口。依赖于该标准的实现就称为POSIXthreads或者Pthreads。现在多数硬件销售商也提供Pthreads,附加于私有的API。•Pthreads被定义为一些C语言类型和函数调用,用pthread.h头(包含)文件和线程库实现。这个库可以是其它库的一部分,如libc。Pthreads概述为什么使用Pthreads?使用Pthreads的主要动机是提高潜在程序的性能。当与创建和管理进程的花费相比,线程可以使用操作系统较少的开销,管理线程需要较少的系统资源。例如,下表比较了fork()函数和pthread_create()函数所用的时间。计时反应了50,000个进程/线程的创建,使用时间工具实现,单位是秒,没有优化标志。备注:不要期待系统和用户时间加起来就是真实时间,因为这些SMP系统有多个CPU同时工作。这些都是近似值。平台fork()pthread_create()realusersysrealusersysAMD2.4GHzOpteron(8cpus/node)41.0760.089.010.660.190.43IBM1.9GHzPOWER5p5-575(8cpus/node)64.2430.7827.681.750.691.10IBM1.5GHzPOWER4104.0548.6447.212.011.001.52(8cpus/node)INTEL2.4GHzXeon(2cpus/node)54.951.5420.781.640.670.90INTEL1.4GHzItanium2(4cpus/node)54.541.0722.222.031.260.67forkvsthread.txt•在同一个进程中的所有线程共享同样的地址空间。较于进程间的通信,在许多情况下线程间的通信效率比较高,且易于使用。•较于没有使用线程的程序,使用线程的应用程序有潜在的性能增益和实际的优点:oCPU使用I/O交叠工作:例如,一个程序可能有一个需要较长时间的I/O操作,当一个线程等待I/O系统调用完成时,CPU可以被其它线程使用。o优先/实时调度:比较重要的任务可以被调度,替换或者中断较低优先级的任务。o异步事件处理:频率和持续时间不确定的任务可以交错。例如,web服务器可以同时为前一个请求传输数据和管理新请求。•考虑在SMP架构上使用Pthreads的主要动机是获的最优的性能。特别的,如果一个程序使用MPI在节点通信,使用Pthreads可以使得节点数据传输得到显著提高。•例如:oMPI库经常用共享内存实现节点任务通信,这至少需要一次内存复制操作(进程到进程)。oPthreads没有中间的内存复制,因为线程和一个进程共享同样的地址空间。没有数据传输。变成cache-to-CPU或memory-to-CPU的带宽(最坏情况),速度是相当的快。o比较如下:PlatformMPISharedMemoryBandwidth(GB/sec)PthreadsWorstCaseMemory-to-CPUBandwidth(GB/sec)AMD2.4GHzOpteron1.25.3IBM1.9GHzPOWER5p5-5754.116IBM1.5GHzP0WER42.14Intel1.4GHzXeon0.34.3Intel1.4GHzItanium21.86.4Pthreads概述使用线程设计程序丫并行编程:•在现代多CPU机器上,pthread非常适于并行编程。可以用于并行程序设计的,也可以用于pthread程序设计。•并行程序要考虑许多,如下:o用什么并行程序设计模型?o问题划分o加载平衡(Loadbalancing)o通信o数据依赖o同步和竞争条件o内存问题oI/O问题o程序复杂度o程序员的努力/花费/时间o…•包含这些主题超出本教程的范围,有兴趣的读者可以快速浏览下“IntroductiontoParallelComputing"教程。•大体上,为了使用Pthreads的优点,必须将任务组织程离散的,独立的,可以并发执行的。例如,如果routinel和routine2可以互换,相互交叉和或者)重叠,他们就可以线程化。routinelroutine?finalroutineroutinelroutine?finalroutineUnalroutineroutinelUnalroutineroutinelroutines•拥有下述特性的程序可以使用pthreads:o工作可以被多个任务同时执行,或者数据可以同时被多个任务操作。o阻塞与潜在的长时间I/O等待。o在某些地方使用很多CPU循环而其他地方没有。o对异步事件必须响应。o一些工作比其他的重要(优先级中断)。•Pthreads也可以用于串行程序,模拟并行执行。很好例子就是经典的web浏览器,对于多数人,运行于单CPU的桌面/膝上机器,许多东西可以同时“显示”出来。•使用线程编程的几种常见模型:o管理者7工作者(ManogeWo/•腕f):—个单线程,作为管理器将工作分配给其它线程(工作者),典型的,管理器处理所有输入和分配工作给其它任务。至少两种形式的manager/worker模型比较常用:静态worker池和动态worker池。o管道(Pipeline):任务可以被划分为一系列子操作,每一个被串行处理,但是不同的线程并发处理。汽车装配线可以很好的描述这个模型。oPeer:和manager/worker模型相似,但是主线程在创建了其它线程后,自己也参与工作。►共享内存模型(SharedMemoryModel):•所有线程可以访问全局,共享内存•线程也有自己私有的数据•程序员负责对全局共享数据的同步存取(保护)
threadthreadprivateprivatethreadthreadprivateprivateI线程安全(Thread-safeness):•线程安全:简短的说,指程序可以同时执行多个线程却不会“破坏“共享数据或者产生“竞争”条件的能力。•例如:假设你的程序创建了几个线程,每一个调用相同的库函数:
o这个库函数存取/修改了一个全局结构或内存中的位置。o当每个线程调用这个函数时,可能同时去修改这个全局结构活内存位置。o如果函数没有使用同步机制去阻止数据破坏,这时,就不是线程安全的了。subA•如果你不是100%确定外部库函数是线程安全的,自己负责所可能引发的问题。•建议:小心使用库或者对象,当不能明确确定是否是线程安全的。若有疑虑,假设其不是线程安全的直到得以证明。可以通过不断地使用不确定的函数找出问题所在。PthreadsAPIPthreadsAPI在ANSI/IEEEPOSIX1003.1T995标准中定义。不像MPI,该标准不是免费的,必须向IEEE购买。PthreadsAPI中的函数可以非正式的划分为三大类:线程管理血management):第一类函数直接用于线程:创建(creating),分离(detaching),连接(joining)等等。包含了用于设置和查询线程属性(可连接,调度属性等)的函数。互斥量GW“te兀eQ:第二类函数是用于线程同步的,称为互斥量(mutexes),是"mutualexclusion"的缩写。Mutex函数提供了创建,销毁,锁定和解锁互斥量的功能。同时还包括了一些用于设定或修改互斥量属性的函数。条件变量Conditionvariables):第三类函数处理共享一个互斥量的线程间的通信,基于程序员指定的条件。这类函数包括指定的条件变量的创建,销毁,等待和受信(signal)。设置查询条件变量属性的函数也包含其中。
•命名约定:线程库中的所有标识符都以pthread开头RoutinePrefixFunctionalGrouppthread_线程本身和各种相关函数pthread_attr_线程属性对象pthread_mutex_互斥量pthread_mutexattr_互斥量属性对象pthread_cond_条件变量pthread_condattr_条件变量属性对象pthread_key_线程数据键(Thread-specificdatakeys)•在API的设计中充满了不透明对象的概念,基本调用可以创建或修改不透明对象。不透明的对象可以被一些属性函数调用修改。•PthreadAPI包含了60多个函数。该教程仅限于一部分(对于刚开始学习Pthread的程序是非常有用的)。•为了可移植性,使用Pthread库时,pthread.h头文件必须在每个源文件中包含。•现行POSIX标准仅定义了C语言的使用。Fortran程序员可以嵌入C函数调用使用,有些Fortran编译器(像IBMAIXFortran)可能提供了FortranpthreadsAPI。•关于Pthreads有些比较优秀的书籍。其中一些在该教程的参考一节列出。编译多线程程序•下表列出了一些编译使用了pthreads库程序的命令:■■Compiler/PlatformCompilerCommandDescriptionxlc_r/cc_rC(ANSI/non-ANSI)IBMxlC_rC++AIXxlf_r-qnosavexlf90_r-qnosaveFortran-usingIBM'sPthreadsAPI(non-portable)INTELicc-pthreadCLinuxicpc-pthreadC++PathScalepathcc-pthreadCLinuxpathCC-pthreadC++PGIpgcc-lpthreadCLinuxpgCC-lpthreadC++GNUgcc-pthreadGNUCLinux,AIXg++-pthreadGNUC++线程管理(ThreadManagement)创建和结束线程l函数:Ipthread_create(thread,attr,start_routine,arg)pthread_exit(status)pthread_attr_init(attr)pthread_attr_destroy(attr)、创建线程:•最初,main函数包含了一个缺省的线程。其它线程则需要程序员显式地创建。pthread_create创建一个新线程并使之运行起来。该函数可以在程序的任何地方调用。pthread_create参数:thread:返回一个不透明的,唯一的新线程标识符。attr:不透明的线程属性对象。可以指定一个线程属性对象,或者NULL为缺省值。start_routine:线程将会执行一次的C函数。arg:传递给start_routii^个参数,传递时必须转换成指向void的指针类型。没有参数传递时,可设置为NULL。一个进程可以创建的线程最大数量取决于系统实现。一旦创建,线程就称为peers,可以创建其它线程。线程之间没有指定的结构和依赖关系。thread3?Q:—个线程被创建后,怎么知道操作系统何时调度该线程使之运行?A:除非使用了Pthreads的调度机制,否则线程何时何地被执行取决于操作系统的实现。强壮的程序应该不依赖于线程执行的顺序。、线程属性:•线程被创建时会带有默认的属性。其中的一些属性可以被程序员用线程属性对象来修改。pthread_attr_init和pthread_attr_destroy用于初始化/销毁先成属性对象。•其它的一些函数用于查询和设置线程属性对象的指定属性。一些属性下面将会讨论。L结束终止:•结束线程的方法有一下几种:O线程从主线程(main函数的初始线程)返回。o线程调用了pthread_exit函数。o其它线程使用pthread_cancel函数结束线程。o调用exec或者exit函数,整个进程结束。•pthread_exit用于显式退出线程。典型地,pthread_exit()函数在线程完成工作时,不在需要时候被调用,退出线程。•如果main()在其他线程创建前用pthread_exit()退出了,其他线程将会继续执行。否则,他们会随着main的结束而终止。•程序员可以可选择的指定终止状态,当任何线程连接(join)该线程时,该状态就返回给连接(join)该线程的线程。•清理:pthread_exit()函数并不会关闭文件,任何在线程中打开的文件将会一直处于打开状态,知道线程结束。•讨论:对于正常退出,可以免于调用pthread_exit()。当然,除非你想返回一个返回值。然而,在main中,有一个问题,就是当main结束时,其它线程还没有被创建。如果此时没有显式的调用pthread_exit(),当main结束时,进程(和所有线程)都会终止。可以在main中调用pthread_exit(),此时尽管在main中已经没有可执行的代码了,进程和所有线程将保持存活状态,。例子:Pthread创建和终止•该例用pthread_create()创建了5个线程。每一个线程都会打印一条“HelloWorld"的消息,然后调用pthread_exit()终止线程。3JExampleCode-PthreadCreationandTermination#include<pthread.h>#include<stdio.h>#defineNUM_THREADS5void*PrintHello(void*threadid){inttid;tid=(int)threadid;printf("HelloWorld!It'sme,thread#%d!\n",tid);pthread_exit(NULL);}intmain(intargc,char*argv[]){pthread_tthreads[NUM_THREADS];intrc,t;for(t=0;t<NUM_THREADS;t++){printf("Inmain:creatingthread%d\n",t);rc=pthread_create(&threads[t],NULL,PrintHello,(void*)t);if(rc){printf("ERROR;returncodefrompthread_create()is%d\n",rc);exit(-l);}}pthread_exit(NULL);}卜Output线程管理向线程传递参数•pthread_create()函数允许程序员想线程的startroutine传递一个参数。当多个参数需要被传递时,可以通过定义一个结构体包含所有要传的参数,然后用pthread_create()传递一个指向改结构体的指针,来打破传递参数的个数的限制。•所有参数都应该传引用传递并转化成(void*)。?Q:怎样安全地向一个新创建的线程传递数据?A:确保所传递的数据是线程安全的(不能被其他线程修改)。下面三个例子演示了那个应该和那个不应该。Example1-ThreadArgumentPassing下面的代码片段演示了如何向一个线程传递一个简单的整数。主线程为每一个线程使用一个唯一的数据结构,确保每个线程传递的参数是完整的。int*taskids[NUM_THREADS];for(t=0;t<NUM_THREADS;t++){taskids[t]二(int*)malloc(sizeof(int));*taskids[t]二t;printf("Creatingthread%d\n",t);rc=pthread_create(&threads[t],NULL,PrintHello,(void*)taskids[t]);0utput10Example2-ThreadArgumentPassing例子展示了用结构体向线程设置/传递参数。每个线程获得一个唯一的结构体实例。struetthread_data{intthread_id;intsum;char*message;};structthread_datathread_data_array[NUM_THREADS];void*PrintHello(void*threadarg){structthread_data*my_data;my_data=(structthread_data*)threadarg;taskid=my_data—>thread_id;sum=my_data—>sum;hello_msg=my_data—>message;}intmain(intargc,char*argv[]){thread_data_array[t].thread_id=t;thread_data_array[t].sum=sum;thread_data_array[t].message=messages]t];rc=pthread_create(&threads[t],NULL,PrintHello,(void*)&thread_data_array[t]);}卜O-utputExample3-ThreadArgumentPassing (Incorrect)例子演示了错误地传递参数。循环会在线程访问传递的参数前改变传递给线程的地址的内容。intrc,t;for(t=0;t<NUM_THREADS;t++){printf("Creatingthread%d\n",t);rc=pthread_create(&threads[t],NULL,PrintHello,
(void*)&t);卜(void*)&t);卜Output线程管理连接(Joining)和分离(Detaching)线程I函数:Ipthread_join(threadid,status)pthread_detach(threadid,status)pthread_attr_setdetachstate(attr,detachstate)pthread_attr_getdetachstate(attr,detachstate)I连接:• “连接”是一种在线程间完成同步的方法。例如:MasterThreadWorkerThreadDOWORKpthreadjoipthread,esc!MasterThreadWorkerThreadDOWORKpthreadjoipthread,esc!Worker
Thread•连接线程只能用pthread_join()连接一次。若多次调用就会发生逻辑错误。两种同步方法,互斥量(mutexes)和条件变量(conditionvariables),稍后讨论。►可连接(JoinableorNot)?•当一个线程被创建,它有一个属性定义了它是可连接的(joinable)还是分离的(detached)。只有是可连接的线程才能被连接(joined),若果创建的线程是分离的,则不能连接。POSIX标准的最终草案指定了线程必须创建成可连接的。然而,并非所有实现都遵循此约定。•使用pthread_create()的attr参数可以显式的创建可连接或分离的线程,典型四步如下:声明一个pthread_attr_t数据类型的线程属性变量用pthread_attr_init()初始化改属性变量用pthread_attr_setdetachstate()设置可分离状态属性完了后,用pthread_attr_destroy()释放属性所占用的库资源I分离(Detaching):pthread_detach()可以显式用于分离线程,尽管创建时是可连接的。•没有与pthread_detach()功能相反的函数I建议:•若线程需要连接,考虑创建时显式设置为可连接的。因为并非所有创建线程的实现都是将线程创建为可连接的。•若事先知道线程从不需要连接,考虑创建线程时将其设置为可分离状态。一些系统资源可能需要释放。例子:PthreadJoiningExampleCode-PthreadJoining这个例子演示了用Pthreadjoin函数去等待线程终止。因为有些实现并不是默认创建线程是可连接状态,例子中显式地将其创建为可连接的。#defineNUM_THREADS3void*BusyWork(void*null){inti;doubleresult=0.0;for(i=0;i<1000000;i++){result二result+(double)random();}printf("result二%e\n",result);pthread_exit((void*)0);}intmain(intargc,char*argv[]){pthread_tthread[NUM_THREADS];pthread_attr_tattr;intrc,t;void*status;/*Initializeandsetthreaddetachedattribute*/pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);for(t=0;t<NUM_THREADS;t++){printf("Creatingthread%d\n",t);rc=pthread_create(&thread[t],&attr,BusyWork,NULL);if(rc){printf("ERROR;returncodefrompthread_create()is%d\n",rc);exit(-l);}}/*Freeattributeandwaitfortheotherthreads*/pthread_attr_destroy(&attr);for(t=0;t<NUM_THREADS;t++){rc=pthread_join(thread[t],&status);if(rc){printf("ERROR;returncodefrompthread_join()is%d\n",rc);exit(-l);}printf("Completedjoinwiththread%dstatus二%ld\n",t,(long)status);}pthread_exit(NULL);}—卜O-utput线程管理栈管理L函数:Ipthread_attr_getstacksize(attr,stacksize)pthread_attr_setstacksize(attr,stacksize)pthread_attr_getstackaddr(attr,stackaddr)pthread_attr_setstackaddr(attr,stackaddr)I防止栈问题:POSIX标准并没有指定线程栈的大小,依赖于实现并随实现变化。•很容易超出默认的栈大小,常见结果:程序终止或者数据损坏。•安全和可移植的程序应该不依赖于默认的栈限制,但是取而代之的是用pthread_attr_setstacksize分配足够的栈大小。pthread_attr_getstackaddr和pthread_attr_setstackaddr函数可以被程序用于将栈设置在指定的内存区域。卜在lc上的一些实际例子:•默认栈大小经常变化很大,最大值也变化很大,可能会依赖于每个节点的线程数目。NodeArchitecture#CPUsMemory(GB)DefaultSize(bytes)AMDOpteron8162,097,152IntelIA644833,554,432IntelIA32242,097,152IBMPower5832196,608IBMPower4816196,608IBMPower316169&304例子:栈管理ExampleCode-StackManagement这个例子演示了如何去查询和设定线程栈大小。#include<pthread.h>#include<stdio.h>#defineNTHREADS4#defineN1000#defineMEGEXTRA1000000pthread_attr_tattr;void*dowork(void*threadid){doubleA[N][N];inti,j,tid;size_tmystacksize;tid=(int)threadid;pthread_attr_getstacksize(&attr,&mystacksize);printf("Thread%d:stacksize=%libytes\n",tid,mystacksize);for(i=0;i<N;i++)for(j=0;j<N;j++)A[i][j]=((i*j)/3.452)+(N-i);pthread_exit(NULL);}intmain(intargc,charpthread_self返回调用该函数的线程的唯一,系统分配的线程ID。pthread_self返回调用该函数的线程的唯一,系统分配的线程ID。 pthread_equal比较两个线程ID,若不同返回0,否则返回非0值。•注意这两个函数中的线程ID对象是不透明的,不是轻易能检查的。因为线程ID是不透明的对象,所以C语言的==操作符不能用于比较两个线程ID。{pthread_tthreads[NTHREADS];size_tstacksize;intrc,t;pthread_attr_init(&attr);pthread_attr_getstacksize(&attr,&stacksize);printf("Defaultstacksize=%li\n",stacksize);stacksize=sizeof(double)*N*N+MEGEXTRA;printf("Amountofstackneededperthread=%li\n",stacksize);pthread_attr_setstacksize(&attr,stacksize);printf("Creatingthreadswithstacksize=%libytes\n",stacksize);for(t=0;t〈NTHREADS;t++){rc=pthread_create(&threads[t],&attr,dowork,(void*)t);if(rc){printf("ERROR;returncodefrompthread_create()is%d\n",rc);exit(-l);}}printf("Created%dthreads.\n",t);pthread_exit(NULL);}其他各种函数:pthread_self()pthread_equal(thread1,thread2)pthread_once(once_control,init_routine)pthread_once在一个进程中仅执行一次init_routine。任何线程第一次调用该函数会执行给定的init_routine,不带参数,任何后续调用都没有效果。init_routine函数一般是初始化的程序once_control参数是一个同步结构体,需要在调用pthread_once前初始化。例如:pthread_once_tonce_control=PTHREAD_ONCE_INIT;互斥量(MutexVariables)概述•互斥量(Mutex)是“皿utualexclusion"的缩写。互斥量是实现线程同步,和保护同时写共享数据的主要方法互斥量对共享数据的保护就像一把锁。在Pthreads中,任何时候仅有一个线程可以锁定互斥量,因此,当多个线程尝试去锁定该互斥量时仅有一个会成功。直到锁定互斥量的线程解锁互斥量后,其他线程才可以去锁定互斥量。线程必须轮着访问受保护数据。互斥量可以防止“竞争"条件。下面的例子是一个银行事务处理时发生了竞争条件:Thread1Thread2BalanceReadbalance:$1000$1000Readbalance:$1000$1000Deposit$200$1000Deposit$200$1000Updatebalance$1000+$200$1200Updatebalance$1000+$200$1200•上面的例子,当一个线程使用共享数据资源时,应该用一个互斥量去锁定“Balance"。•一个拥有互斥量的线程经常用于更新全局变量。确保了多个线程更新同样的变量以安全的方式运行,最终的结果和一个线程处理的结果是相同的。这个更新的变量属于一个“临界区(criticalsection)"。•使用互斥量的典型顺序如下:o创建和初始一个互斥量o多个线程尝试去锁定该互斥量o仅有一个线程可以成功锁定改互斥量o锁定成功的线程做一些处理o线程解锁该互斥量o另外一个线程获得互斥量,重复上述过程o最后销毁互斥量•当多个线程竞争同一个互斥量时,失败的线程会阻塞在lock调用处。可以用“trylock”替换“lock",则失败时不会阻塞。•当保护共享数据时,程序员有责任去确认是否需要使用互斥量。如,若四个线程会更新同样的数据,但仅有一个线程用了互斥量,则数据可能会损坏。互斥量(MutexVariables)创建和销毁互斥量l函数:Ipthread_mutex_init(mutex,attr)pthread_mutex_destroy(mutex)pthread_mutexattr_init(attr)pthread_mutexattr_destroy(attr)•互斥量必须用类型pthread_mutex_t类型声明,在使用前必须初始化,这里有两种方法可以初始化互斥量:声明时静态地,如:pthread_mutex_tmymutex=PTHREAD_MUTEX_INITIALIZER;动态地用pthread_mutex_init()函数,这种方法允许设定互斥量的属性对象attr。互斥量初始化后是解锁的。attr对象用于设置互斥量对象的属性,使用时必须声明为pthread_mutextattr_t类型,默认值可以是NULL。Pthreads标准定义了三种可选的互斥量属性:o协议(Protocol):指定了协议用于阻止互斥量的优先级改变o优先级上限(Prioceiling):指定互斥量的优先级上限o进程共享(Process-shared):指定进程共享互斥量注意所有实现都提供了这三个可先的互斥量属性。pthread_mutexattr_init()和pthread_mutexattr_destroy()函数分另U用于创建和销毁互斥量属性对象。pthread_mutex_destroy()应该用于释放不需要再使用的互斥量对象。互斥量(MutexVariables)锁定和解锁互斥量I函数:Ipthread_mutex_lock(mutex)pthread__mutex_trylock(mutex)pthread_mutex_unlock(mutex)I用法:线程用pthread_mutex_lock()函数去锁定指定的mutex变量,若该mutex已经被另外一个线程锁定了,该调用将会阻塞线程直到mutex被解锁。pthread_mutex_trylock()willattempttolockamutex.However,fthemutexisalreadylocked,theroutinewillreturnimmediatelywitha"busy"errorcode.Thisroutinemaybeusefulinpthread_mutex_trylock()尝试着去锁定一个互斥量,然而,若互斥量已被锁定,程序会立刻返回并返回一个忙错误值。该函数在优先级改变情况下阻止死锁是非常有用的。•线程可以用pthread_mutex_unlock()解锁自己占用的互斥量。在一个线程完成对保护数据的使用,而其它线程要获得互斥量在保护数据上工作时,可以调用该函数。若有一下情形则会发生错误:o互斥量已经被解锁o互斥量被另一个线程占用•互斥量并没有多么“神奇”的,实际上,它们就是参与的线程的“君子约定”。写代码时要确信正确地锁定,解锁互斥量。下面演示了一种逻辑错误:Thread1Thread2Thread3LockLockA=2A=A+1A=A*BUnlockUnlock?Q:有多个线程等待同一个锁定的互斥量,当互斥量被解锁后,那个线程会第一个锁定互斥量?A:除非线程使用了优先级调度机制,否则,线程会被系统调度器去分配,那个线程会第一个锁定互斥量是随机的。例子:使用互斥量ExampleCode-UsingMutexes例程演示了线程使用互斥量处理一个点积(dotproduct)计算。主数据通过一个可全局访问的数据结构被所有线程使用,每个线程处理数据的不同部分,主线程等待其他线程完成计算并输出结果。#include<pthread.h>#include<stdio.h>#include<malloc.h>/*Thefollowingstructurecontainsthenecessaryinformationtoallowthefunction"dotprod"toaccessitsinputdataandplaceitsoutputintothestructure.*/typedefstruct{TOC\o"1-5"\h\zdouble *a;double *b;double sum;intveclen;}DOTDATA;/*Definegloballyaccessiblevariablesandamutex*/#defineNUMTHRDS4#defineVECLEN100DOTDATAdotstr;pthread_tcallThd[NUMTHRDS];pthread_mutex_tmutexsum;/*Thefunctiondotprodisactivatedwhenthethreadiscreated.AllinputtothisroutineisobtainedfromastructureoftypeDOTDATAandalloutputfromthisfunctioniswrittenintothisstructure.Thebenefitofthisapproachisapparentforthmulti—threadedprogram:whenathreadiscreatedwepassasingleargumenttotheactivatedfunction—typicallythisargumentisathreadnumber.Alltheotherinformationrequiredbythefunctionisaccessedfromthegloballyaccessiblestructure.*/void*dotprod(void*arg){/*Defineanduselocalvariablesforconvenience*/inti,start,end,offset,len;doublemysum,*x,*y;offset二(int)arg;len=dotstr.veclen;start二offset*len;end=start+len;x=dotstr.a;y=dotstr.b;/*Performthedotproductandassignresulttotheappropriatevariableinthestructure.*/mysum=0;for(i=start;i<end;i++){mysum+=(x[i]*y[i]);}/*Lockamutexpriortoupdatingthevalueinthesharedstrueture,andunlockituponupdating.*/pthread_mutex_loek(&mutexsum);dotstr.sum+=mysum;pthread_mutex_unlock(&mutexsum);pthread_exit((void*)0);}/*Themainprogramcreatesthreadswhichdoalltheworkandthenprintoutresultuponcompletion.Beforecreatingthethreads,theinputdataiscreated.Sinceallthreadsupdateasharedstructure,weneedamutexformutualexclusion.Themainthreadneedstowaitforallthreadstocomplete,itwaitsforeachoneofthethreads.Wespecifyathreadattributevaluethatallowthemainthreadtojoinwitthethreadsitcreates.Notealsothatwefreeuphandleswhentheyarenolongerneeded.*/intmain(intargc,char*argv[]){inti;double*a,*b;void*status;pthread_attr_tattr;/*Assignstorageandinitializevalues*/a=(double*)malloc(NUMTHRDS*VECLEN*sizeof(double));b=(double*)malloc(NUMTHRDS*VECLEN*sizeof(double));for(i=0;i<VECLEN*NUMTHRDS;i++){a[i]=1.0;b[i]=a[i];}dotstr.veclen=VECLEN;dotstr.a=a;
dotstr.b=b;dotstr.sum=0;pthread_mutex_init(&mutexsum,NULL);/*Createthreadstoperformthedotproduct*/pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);for(i=0;i<NUMTHRDS;i++){/*Eachthreadworksonadifferentsetofdata.Theoffsetisspecifiedby'i'.ThesizeofthedataforeachthreadisindicatedbyVECLEN.*/pthread_create(&callThd[i],&attr,dotprod,(void*)i)}pthread_attr_destroy(&attr);/*Waitontheotherthreads*/for(i=0;i<NUMTHRDS;i++){pthread_join(callThd[i],&status);}/*Afterjoining,printouttheresultsandcleanup*/printf("Sum=%f\n",dotstr.sum);free(a);free(b);pthread_mutex_destroy(&mutexsum);pthread_exit(NULL);}、Source、SourceSerialversionPthreadsversion条件变量(ConditionVariables)
概述•条件变量提供了另一种同步的方式。互斥量通过控制对数据的访问实现了同步,而条件变量允许根据实际的数据值来实现同步。•没有条件变量,程序员就必须使用线程去轮询(可能在临界区),查看条件是否满足。这样比较消耗资源,因为线程连续繁忙工作。条件变量是一种可以实现这种轮询的方式。•条件变量往往和互斥一起使用•使用条件变量的代表性顺序如下:主线程(MainThread)oooo声明和初始化需要同步的全局数据/变量(如“oooo创建工作线程A和BThreadAThreadBo工作,一直到一定的o工作条件满足(如“count”等于o锁定相关互斥量一个指定的值)o改变Thread-A所等
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 河南省周口西华县联考2025届八年级物理第二学期期末考试模拟试题含解析
- 2025年广东佛山市西南水都饮料基地投资有限公司招聘笔试参考题库含答案解析
- 安全文化概述课件
- 2025年广东省科学院梅州产业技术研究院有限公司招聘笔试参考题库含答案解析
- 2025年新疆克拉玛依市鑫塔物业服务有限责任公司招聘笔试参考题库含答案解析
- 2025年江苏江苏射阳港港口集团有限公司招聘笔试参考题库附带答案详解
- 小朋友国防教育课件
- 山东中考英语真题单选题100道及答案
- 四川省 2023-2024学年高二上学期11月月考数学 无答案
- 重庆市乌江新高考协作体2022-2023学年高一下学期期末联考语文试题 无答案
- 2025安徽蚌埠市龙子湖区产业发展有限公司招聘22人笔试参考题库附带答案详解
- 偿二代下我国财险公司偿付能力影响因素的深度剖析与实证研究
- 【MOOC】理解马克思-南京大学 中国大学慕课MOOC答案
- JGT266-2011 泡沫混凝土标准规范
- 配电室运行维护投标方案(技术标)
- 禾川x3系列伺服说明书
- 常用H型钢理论重量表格
- 中学自主招生考试物理试题
- 四川大学-刘龙飞-毕业答辩PPT模板
- 工作分析试题及答案
- 突发事件应急演练指南
评论
0/150
提交评论