多核程序设计Multi-coreProgramming提纲线程多线程编程的问题Windows多线程编程技术1.多核、多处理器系统中的每颗处理器(每个核),同时间内可以执行各自不同的进程(或线程)。2.一颗单线程能力的处理器(一个核)只能执行一个进程,双核处理器就能够同时执行两个不同的进程(或线程),四核就可以同时不同的四个进程(或线程)。3.倘若是执行不支持多线程的程序,那么每颗处理器内的多线程功效就无从发挥。4、硬件条件已经具备,但作为人机交互的操作系统和应用软件需要研究它的并行化方法,最大限度挖掘系统的整体性能。多核与多线程编程多核与多线程编程发挥软件作用,充分利用系统(CPU)资源,提高使用效率欲说线程,先从进程(Process)说起进程是操作系统结构的基础;是一个正在执行的程序,是计算机中正在运行的程序实例,是可以分配给处理器并由处理器执行的一个实体;由单一顺序的执行显示,一个当前状态和一组相关的系统资源所描述的活动单元。进程为应用程序的运行实例,是应用程序的一次动态执行。我们可以简单地理解为:它是操作系统当前运行的执行程序。对应用程序来说,进程就像一个大容器。在应用程序被运行后,就相当于将应用程序装进容器里了,你可以往容器里加其他东西(如:应用程序在运行时所需的变量数据、需要引用的DLL文件等),当应用程序被运行两次时,容器里的东西并不会被倒掉,系统会找一个新的进程容器来容纳它。多线程的概念线程(thread)是进程上下文(context)中执行的代码序列,是进程中的一个实体,是被系统独立调度和分派的基本单位,又被称为轻量级进程(lightweightprocess)在支持多线程的系统中,进程成为资源分配和保护的实体,而线程是被调度执行的基本单元。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。代码数据文件寄存器栈线程代码数据文件寄存器栈寄存器栈寄存器栈线程进程与线程的关系程序在操作系统中作为进程方式存在、获取资源、运行。在一个进程内,线程可以创建其它线程。每个线程有各自的栈(stack)。一个进程内所有的线程共享代码段和数据段。进程一个进程内的线程示例对于在一个进程内的线程:一个线程对共享的系统资源进行修改,在这个进程内的其它线程也可以见到这种修改。进程内的多个线程可以对同一个内存单元进行读和写操作,所以必须要采取显式同步机制。在同一个进程的地址空间下,线程间的通信消耗更小。线程的状态线程的状态就绪(ready):线程等待可用的处理器。运行(running):线程正在被执行。阻塞(blocked):线程正在等待某个事件的发生(比如I/O的完成,试图加锁一个被上锁的互斥量)。终止(terminated):线程从起始函数中返回或者调用pthread_exit。终止运行阻塞就绪调度条件满足等待资源切换被创建完成多线程编程的问题多核与线程并行的关系在单核平台上的线程并发:在各个核上可以实现线程并行:CPU核核1核2线程的同步由于线程共享同一进程的内存空间,多个线程可能需要同时访问同一个数据。如果没有正确的保护措施,对共享数据的访问会造成数据的不一致和错误。全局变量是线程间通信最常用的手段,然而却容易造成访问冲突。常用的同步机制:互斥(mutualexclusion)条件同步(conditionsynchronization)线程的本地存储(threadlocalstorage,tls)线程同步问题示例一个银行系统中有两个线程执行取款操作,一个使用ATM机,另一个使用存折在柜台取款。会怎样?如果不加以控制,会使得账户余额为负数。互斥和同步互斥是指对于共享的操作系统资源(广义的资源,譬如全局变量),在各线程访问时的排它性。临界区(criticalsection)锁(lock)/互斥量(mutex)信号量(semaphore)同步是指线程间的一种制约关系,一个线程的执行依赖另一个线程的消息。当它没有得到消息时应等待,直到消息到达时被唤醒。事件(event)信号量(semaphore)临界区(1)示例intGlobal_Sum;DWORDWINAPIthreadFunc(LPVOIDarg){intmySum=bigCompute();Global_Sum+=mySum;return0;}voidmain(){Global_Sum=0;for(inti=0;i4;i++)hThread[i]=CreateThread(NULL,0,threadFunc,NULL,0,NULL);WaitForMultipleObjects(4,hThread,TRUE,INFINITE);}此程序段为包含共享数据Global_Sum的一段代码,称为临界区。多个线程同时运行,有可能造成写-写冲突。让一个线程执行这段代码时,其它线程都不能执行,保护起来!怎么办?CRITICAL_SECTIONg_cs;EnterCriticalSection(&g_cs);LeaveCriticalSection(&g_cs);InitializeCriticalSection(&g_cs);DeleteCriticalSection(&g_cs);临界区(2)如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。临界区包含两个操作原语:EnterCriticalSection();LeaveCriticalSection()EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则,临界区保护的共享资源将永远不会被释放。关于临界区的使用,有下列注意点:虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。每个共享资源使用一个CRITICAL_SECTION变量;不要长时间运行关键代码段,当一个关键代码段长时间运行时,其他线程就会进入等待状态,这会降低应用程序的运行性能;如果需要同时访问多个资源,则可能连续调用EnterCriticalSection;CriticalSection不是OS核心对象,如果进入临界区的线程挂了,将无法释放临界资源。这个缺点在Mutex中得到了弥补。锁(互斥量)(1)锁(互斥量)跟临界区很相似,只有拥有锁(互斥对象)的线程才具有访问资源的权限。区别是:Mutex所花费的时间比CriticalSection多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定TIMEOUT,不会像CriticalSection那样无法得知临界区域的情况,而一直死等。一个锁最多只能由一个线程获得,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。任何线程对共享资源进行操作访问前必须先获得锁,否则,线程将保持在该锁地等待队列,直到该锁被释放。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。锁(2)-示例ThreadAvoidsome_method(){printf(“Ahelloone”);printf(“Ahellotwo”);}ThreadBvoidsome_method(){printf(“Bhelloone”);printf(“Bhellotwo”);}可能的输出结果AhellooneBhellotwoBhellooneAhellotwo怎么办?能控制线程次序么?哪个线程捕获到锁哪个运行,其它线程等待!Mutexmutex;//定义一个锁变量mutex.lock();//获取锁mutex.unlock();//释放锁mutex.lock();//获取锁mutex.unlock();//释放锁AhellooneAhellotwoBhellooneBhellotwoBhellooneBhellotwoAhellooneAhellotwo锁(3)--互锁怎么办?互锁问题的产生生产者-消费者问题若消费者线程先捕获了锁,…一种解决途径条件变量信号量(semaphore)(1)历史最悠久的同步方法Dijkstra在1965年提出PV操作(原子操作)信号量是解决producer/consumer问题的关键要素。意义信号量Semaphore是一个整数值,代表目前可用的资源数。系统同步初始可以设有多少资源,即可以将信号量初值设为0到某个最大值之间的任意数。如果Semaphore的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。信号量状态在其计数0时有信号,即当前资源的数量0,信号量有效。0时无信号,即当前资源的数量是0,信号量无效。系统不允许当前资源的数量为负值,即不能在信号量为负的情况下执行任务。V操作时也不能超过最大值。核心对象,可以跨进程访问。信号量(2)—示例保护共享资源DWORDWINAPIthreadFunc(LPVOIDarg){intmySum=bigCompute();Global_Sum+=mySum;return0;}DWORDWINAPIthreadFunc(LPVOIDarg){intmySum=bigCompute();Global_Sum+=mySum;return0;}thread1thread2Semaphoresem(1);//创建信号量变量sem,并设其初值为1P(sem);//若sem0,sem减1P(sem);//若sem0,sem减1V(sem);//sem加1并判断V(sem);//sem加1并判断信号量已经小于等于0的线程会进入等待队列关键在于P(sem)时,即减和判断期间,不允许其他线程打断,即原子操作。信号量(3)—示例同步线程---生产者消费者问题thread_producer(LPVOIDarg){intmySum1=bigCompute();Global_Sum+=mySum1;return0;}thread_Consumer(LPVOIDarg){intmySum2;mySum2*=Global_Sum;return0;}thread1thread2Semaphoresem(0);//创建信号量变量sem,并设其初值为0P(sem);//若sem0,sem减1V(sem);//sem加1并判断运算次序:mySum2=(Global_Sum+mySum1)*mySum2事件(Event)(1)作用:同步线程用事件(Event)来同步线程最具弹性(灵活)一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。事件类型手动重置事件手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。事件(2)—示例“读-写”线程同步VoidReadThread(LPVOIDp){coutGlobalSumendl;}VoidWrite