主要题目类型:1.概念性题目 2,选择性题目,3.简答或综合性题目
一.概念性问答题
1. 线程的基本概念、线程的基本状态及状态之间的关系?
解答:
线程是一个进程内部的一个控制序列。进程至少有一个执行线程。线程是独立调度和分派的基本单位。
注意fork()系统调用和创建新线程的区别。执行fork()系统调用时,复制父进程的一个副本,子进程是独立的一个进程,它的执行几乎完全独立于父进程。而执行pthread_create()系统调用时,在进程中创建一个新线程,但这个线程不是完全独立于父进程的,共享全局变量,但新线程可以有自己的局部变量,即可以拥有自己的
栈。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
在solaris中,分成:
- 用户线程(user-level threads):在用户空间运行。
- 轻量化进程(lightweight processes):主要工作是将用户线程映射到核心线程
- 核心线程(kernel threads):在内核中运行。
线程有四种基本状态,分别为:
- 产生(spawn)
- 中断(block)
- 非中断(unblock)
- 退出(finish)
又作:新生new, 阻塞block, 非阻塞unblock(可运行runable,正在运行running),死亡dead
第一题:线程的基本概念、线程的基本状态及状态之间的关系?
线程,有时称为轻量级进程,是CPU使用的基本单元;它由线程ID、程序计数器、寄存器集合和堆栈组成。它与属于同一进程的其他线程共享其代码段、数据段和其他操作系统资源(如打开文件和信号)。
线程有四种状态:新生状态、可运行状态、被阻塞状态、死亡状态。状态之间的转换如下图所示:
进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握。
最近,我读到一篇材料,发现有一个很好的类比,可以把它们解释地清晰易懂。
1.
计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
2.
假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。
3.
进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
4.
一个车间里,可以有很多工人。他们协同完成一个任务。
5.
线程就好比车间里的工人。一个进程可以包括多个线程。
6.
车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
7.
可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
8.
一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
9.
还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
10.
这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
11.
操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
(完)
2. 线程与进程的区别?
程序: 完成某种功能的一段代码。静态概念。
任务: 程序完成的一个活动。既可以是一个进程,也可以是一个线程。
进程: 程序的一次执行过程。一个独立的进程在内存中有自己的数据空间和代码空间。它所拥有的数据变量属于它自己。
线程: 线程是进程的一个执行序列。同一个进程创建的多个线程可以共享进程的一些资源,如全局变量,同时,多个线程可以有自己的栈空间,线程的局部变量只属于这个线程。
线程可以有自己的堆栈和局部变量,但线程没有单独的地址空间,因此一个线程死掉就等于整个进程死掉,所以线程的健壮性不如进程。但进程切换时,系统开销大,因此
资源利用率低。
对于一些要求同时运行且要共享资源的并发操作,只能用线程,而不能用进程。
总结:1. 一个线程只能同时属于一个进程,一个进程可以有多个线程,且至少有一个线程。
2. 同一进程的所有线程共享进程的所有资源。有些资源的访问需要线程之间互斥访问。
3. 真正在处理机上运行的是线程,而不是进程。
4. 同步问题: 不同进程的线程之间利用消息通信的方式实现同步。
第二题:线程与进程的区别?
1、 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。
2、 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个进程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
3、 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,出了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
4、 与进程的控制表PCB相似,线程也有自己的控制表TCB,但是TCB中所保存的线程状态比PCB表中少多了。
5、 进程是系统所有资源分配时候的一个基本单位,拥有一个完整的虚拟空间地址,并不依赖线程而独立存在。
3. 多线程有几种实现方法,都是什么?
第三题:多线程有几种实现方法,都是什么?
1. 继承 Thread 类
2. 实现 Runnable 接口再 new Thread(YourRunnableOjbect)
4. 多线程同步和互斥有几种实现方法,都是什么?
我在参加2011年迅雷校园招聘时的一面和二面都被问到这个题目,回答的好将会给面试成绩加不少分。
线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。
5. 多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明。
线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。
二、选择性题目
(Baidu)以下多线程对int型变量x的操作,哪几个不需要进行同步: (D),哪几个需要进行同步(ABC)
A. x=y; B. x++; C. ++x; D. x=1;
将代码汇编,得到:
x = y;
00411A25 mov eax,dword ptr [y]
00411A28 mov dword ptr [x],eax
x++;
00411A2B mov eax,dword ptr [x]
00411A2E add eax,1
00411A31 mov dword ptr [x],eax
++x;
00411A34 mov eax,dword ptr [x]
00411A37 add eax,1
00411A3A mov dword ptr [x],eax
x = 1;
00411A3D mov dword ptr [x],1
我们看到ABC都对2个寄存器进行操作。
下表是一个多线程加锁的规律表
操作的结果与初值无关 | 操作的结果与初值相关 | |
写简单数据类型 | 不需要加锁① | 需要加锁② |
写复杂数据类型 | 需要加锁③ | 需要加锁④ |
读简单数据类型 | 不需要加锁⑤ | 不需要加锁⑥ |
读复杂数据类型 | 需要加锁⑦ | 需要加锁⑧ |
ABC都是写简单数据类型 并且操作的结果与初值相关。所以需要加锁。即要求同步
典型代表是“i++;”,需要对它加锁是因为它表面上虽然只有一条语句,却要执行至少两个操作,一是读出i的初始, 二是把加一后的结果写回去,两个操作就没有“原子性”了,所以需要加锁.
(1)很显然,x=1是原子操作。
因为x是int类型,32位CPU上int占32位,在X86上由硬件直接提供了原子性支持。实际上不管有多少个线程同时执行类似x=1这样的赋值语句,x的值最终还是被赋的值(而不会出现例如某个线程只更新了x的低16位然后被阻塞,另一个线程紧接着又更新了x的低24位然后又被阻塞,从而出现x的值被损坏了的情况)。
(2)再来看x++和++x。
其实类似x++, x+=2, ++x这样的操作在多线程环境下是需要同步的。因为X86会按三条指令的形式来处理这种语句:从内存中读x的值到寄存器中,对寄存器加1,再把新值写回x 所处的内存地址(见上面的反汇编代码)。
例如有两个线程,它们按照如下顺序执行(注意读x和写回x是原子操作,两个线程不能同时执行):
time Thread 1 Thread 2
0 load eax, x
1 load eax x
2 add eax, 1 add eax, 1
3 store x, eax
4 store x, eax
我们会发现最终x的值会是1而不是2,因为Thread 1的结果被覆盖掉了。这样情况下我们就需要对x++这样的操作加锁(例如Pthread中的mutex)来保证同步,或者使用一些提供了atomic operations的库(例如Windows API中的atomic 库 ,Linux内核中的atomic.h ,Java concurrent库中的Atomic Integer,C++0x中即将支持的atomic_int等等,这些库会利用CPU提供的硬件机制做一层封装,提供一些保证了原子性的API)。
(3)最后来看看x=y。
在X86上它包含两个操作:读取y至寄存器,再把该值写入x。读y的值这个操作本身是原子的,把值写入x也是原子的,但是两者合起来是不是原子操作呢?我个人认为x=y不是原子操作,因为它不是不可再分的操作。但是它需要不需要同步呢?其实问题的关键在于程序的上下文。
例如有两个线程,线程1要执行{y = 1; x = y;},线程2要执行{y = 2; y = 3;},假设它们按如下时间顺序执行:
time Thread 1 Thread 2
0 store y, 1
1 store y, 2
2 load eax, y
3 store y, 3
4 store x, eax
那么最终线程1中x的值为2,而不是它原本想要的1。我们需要加上相应的同步语句确保y = 2不会在线程1的两条语句之间发生。y = 3那条语句尽管在load y和store x之间执行,但是却不影响x=y这条语句本身的语义。所以你可以说x=y需要同步,也可以说x=y不需要同步,看你怎么理解题意了。x=1是否需要同步也是一样的道理,虽然它本身是原子操作,但是如果有另一个线程要读x=1之后的值,那肯定也需要同步,否则另一个线程读到的就是x的旧值而不是1了。
(alibaba)多线程中栈与堆是公有的还是私有的()
A:栈公有, 堆私有
B:栈公有,堆公有
C:栈私有, 堆公有
D:栈私有,堆私有
分析: 同一进程中每个线程可以有自己的栈空间,因此栈是私有的。堆由进程分配的,对进程内的线程来讲是公有的。
三、综合题目
1. 在Windows编程中互斥量与临界区比较类似,请分析一下二者的主要区别。
两者都可以用于同一进程中不同子线程对资源的互斥访问。
临界区是用户模式下的互斥访问手段,互斥量是内核模式下的互斥访问手段。
critical section: 保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线 程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操 作共享资源的目的。
临界区包含两个操作原语:
EnterCriticalSection() 进入临界区
LeaveCriticalSection() 离开临界区
EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的 LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本 进程内的线程,而不可用来同步多个进程中的线程。
mutex: 互斥量是内核对象,因此还可以用于不同进程中子线程对资源的互斥访问。互斥量可以很好的解决由于线程意外终止资源无法释放的问题。
2. 临界区、互斥量、信号、时间的比较分析。
四种进程或线程同步互斥的控制方法
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
critical section: 保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线 程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操 作共享资源的目的。
临界区包含两个操作原语:
EnterCriticalSection() 进入临界区
LeaveCriticalSection() 离开临界区
EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的 LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本 进程内的线程,而不可用来同步多个进程中的线程。
MFC提供了很多功能完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是 非常简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代 码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。
mutex:互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程 所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同 一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
互斥量包含的几个操作原语:
CreateMutex() 创建一个互斥量
OpenMutex() 打开一个互斥量
ReleaseMutex() 释放互斥量
WaitForMultipleObjects() 等待互斥量对象
同样MFC为互斥量提供有一个CMutex类。使用CMutex类实现互斥量操作非常简单,但是要特别注意对CMutex的构造函数的调用
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)
不用的参数不能乱填,乱填会出现一些意想不到的运行结果。
semaphore: 信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程 最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量 时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数 就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目, 不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可 用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。
P操作 申请资源:
(1)S减1;
(2)若S减1后仍大于等于零,则进程继续执行;
(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
V操作 释放资源:
(1)S加1;
(2)若相加结果大于零,则进程继续执行;
(3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。
信号量包含的几个操作原语:
CreateSemaphore() 创建一个信号量
OpenSemaphore() 打开一个信号量
ReleaseSemaphore() 释放信号量
WaitForSingleObject() 等待信号量
event: 事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
信号量包含的几个操作原语:
CreateEvent() 创建一个信号量
OpenEvent() 打开一个事件
SetEvent() 回置事件
WaitForSingleObject() 等待一个事件
WaitForMultipleObjects() 等待多个事件
WaitForMultipleObjects 函数原型:
WaitForMultipleObjects(
IN DWORD nCount, // 等待句柄数
IN CONST HANDLE *lpHandles, //指向句柄数组
IN BOOL bWaitAll, //是否完全等待标志
IN DWORD dwMilliseconds //等待时间
)
参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核 对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。 dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回 WAIT_TIMEOUT。
总结:
1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使 用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但 对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和 线程退出。
3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根 据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数 器。
3. 一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。
int tally = 0; //glable
void ThreadProc(void)
{
for(inti = 1; i <= 50; i++)
tally += 1;
}
答:[50,100]
4. 子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到主线程又循环 100 次,如此循环50次,试写出代码。
5. (thunder)编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。
思路:用信号量进行各个子线程之间的互斥,创建3个信号量A、B、C。初始时A的资源数为1,B、C的资源数为0,访问A之后,将B的资源数加1,访问B之后将C的资源数加1,访问C之后将A的资源数加1。创建3个子线程顺序访问资源A、B、C。
6. (google)有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推.........现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式:
A:1 2 3 4 1 2....
B:2 3 4 1 2 3....
C:3 4 1 2 3 4....
D:4 1 2 3 4 1....
请设计程序。
7. 生产者消费者问题:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,所有生产者和消费者都是异步方式运行的,但它们必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经装满产品且尚未被取走的缓冲区中投放产品。
分析:假设1个生产者,2个消费者,缓冲区大小为4。
第一.从缓冲区取出产品和向缓冲区投放产品必须是互斥进行的。可以用关键段和互斥量来完成。
第二.生产者要等待缓冲区为空,这样才可以投放产品,消费者要等待缓冲区不为空,这样才可以取出产品进行消费。并且由于有二个等待过程,所以要用二个事件或信号量来控制。
1 //1生产者 2消费者 4缓冲区 2 #include "stdafx.h" 3 #include "stdio.h" 4 #include "stdlib.h" 5 #include <iostream> 6 #include <string> 7 #include <stack> 8 #include <windows.h> 9 #include <process.h> 10 using namespace std; 11 12 //设置控制台输出颜色 13 BOOL SetConsoleColor(WORD wAttributes) 14 { 15 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 16 if (hConsole == INVALID_HANDLE_VALUE) 17 return FALSE; 18 19 return SetConsoleTextAttribute(hConsole, wAttributes); 20 } 21 22 const int END_PRODUCE_NUMBER = 8; //生产产品个数 23 const int BUFFER_SIZE = 4; //缓冲区个数 24 int g_Buffer[BUFFER_SIZE]; //缓冲池 25 int g_i, g_j; 26 CRITICAL_SECTION g_cs; //信号量与关键段 27 HANDLE g_hSemaphoreBufferEmpty, g_hSemaphoreBufferFull; 28 29 //生产者线程函数 30 unsigned int __stdcall ProducerThreadFun(PVOID pM) 31 { 32 for (int i = 1; i <= END_PRODUCE_NUMBER; i++) 33 { 34 //等待有空的缓冲区出现 35 WaitForSingleObject(g_hSemaphoreBufferEmpty, INFINITE); 36 37 //互斥的访问缓冲区 38 EnterCriticalSection(&g_cs); 39 g_Buffer[g_i] = i; 40 printf("生产者在缓冲池第%d个缓冲区中投放数据%d\n", g_i, g_Buffer[g_i]); 41 g_i = (g_i + 1) % BUFFER_SIZE; 42 LeaveCriticalSection(&g_cs); 43 44 //通知消费者有新数据了 45 ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL); 46 } 47 printf("生产者完成任务,线程结束运行\n"); 48 return 0; 49 } 50 51 //消费者线程函数 52 unsigned int __stdcall ConsumerThreadFun(PVOID pM) 53 { 54 while (true) 55 { 56 //等待非空的缓冲区出现 57 WaitForSingleObject(g_hSemaphoreBufferFull, INFINITE); 58 59 //互斥的访问缓冲区 60 EnterCriticalSection(&g_cs); 61 SetConsoleColor(FOREGROUND_GREEN); 62 printf(" 编号为%d的消费者从缓冲池中第%d个缓冲区取出数据%d\n", GetCurrentThreadId(), g_j, g_Buffer[g_j]); 63 SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 64 if (g_Buffer[g_j] == END_PRODUCE_NUMBER)//结束标志 65 { 66 LeaveCriticalSection(&g_cs); 67 //通知其它消费者有新数据了(结束标志) 68 ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL); 69 break; 70 } 71 g_j = (g_j + 1) % BUFFER_SIZE; 72 LeaveCriticalSection(&g_cs); 73 74 Sleep(50); //some other work to do 75 76 ReleaseSemaphore(g_hSemaphoreBufferEmpty, 1, NULL); 77 } 78 SetConsoleColor(FOREGROUND_GREEN); 79 printf(" 编号为%d的消费者收到通知,线程结束运行\n", GetCurrentThreadId()); 80 SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 81 return 0; 82 } 83 84 int main() 85 { 86 InitializeCriticalSection(&g_cs); 87 //初始化信号量,一个记录有产品的缓冲区个数,另一个记录空缓冲区个数. 88 g_hSemaphoreBufferEmpty = CreateSemaphore(NULL, 4, 4, NULL); 89 g_hSemaphoreBufferFull = CreateSemaphore(NULL, 0, 4, NULL); 90 g_i = 0; 91 g_j = 0; 92 memset(g_Buffer, 0, sizeof(g_Buffer)); 93 94 const int THREADNUM = 3; 95 HANDLE hThread[THREADNUM]; 96 //生产者线程 97 hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL); 98 //消费者线程 99 hThread[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL); 100 hThread[2] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL); 101 WaitForMultipleObjects(THREADNUM, hThread, TRUE, INFINITE); 102 103 for (int i = 0; i < THREADNUM; i++) 104 CloseHandle(hThread[i]); 105 106 //销毁信号量和关键段 107 CloseHandle(g_hSemaphoreBufferEmpty); 108 CloseHandle(g_hSemaphoreBufferFull); 109 DeleteCriticalSection(&g_cs); 110 return 0; 111 }
8. 读者写者问题:这也是一个非常经典的多线程题目,题目大意如下:有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者读时写者也不能写。
分析:首先来找找哪些是属于“等待”情况。
第一、写者要等到没有读者时才能去写文件。
第二、所有读者要等待写者完成写文件后才能去读文件。
找完“等待”情况后,再看看有没有要互斥访问的资源。由于只有一个写者而读者们是可以共享的读文件,所以按题目要求并没有需要互斥访问的资源。
1 #include "stdafx.h" 2 #include "stdio.h" 3 #include "stdlib.h" 4 #include <iostream> 5 #include <string> 6 #include <stack> 7 #include <windows.h> 8 #include <process.h> 9 using namespace std; 10 11 //读者与写者问题 12 #include <stdio.h> 13 #include <process.h> 14 #include <windows.h> 15 //设置控制台输出颜色 16 BOOL SetConsoleColor(WORD wAttributes) 17 { 18 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 19 if (hConsole == INVALID_HANDLE_VALUE) 20 return FALSE; 21 22 return SetConsoleTextAttribute(hConsole, wAttributes); 23 } 24 const int READER_NUM = 5; //读者个数 25 //关键段和事件 26 CRITICAL_SECTION g_cs, g_cs_writer_count; 27 HANDLE g_hEventWriter, g_hEventNoReader; 28 int g_nReaderCount; 29 //读者线程输出函数(变参函数的实现) 30 void ReaderPrintf(char *pszFormat, ...) 31 { 32 va_list pArgList; 33 34 va_start(pArgList, pszFormat); 35 EnterCriticalSection(&g_cs); 36 vfprintf(stdout, pszFormat, pArgList); 37 LeaveCriticalSection(&g_cs); 38 va_end(pArgList); 39 } 40 //读者线程函数 41 unsigned int __stdcall ReaderThreadFun(PVOID pM) 42 { 43 ReaderPrintf(" 编号为%d的读者进入等待中...\n", GetCurrentThreadId()); 44 //等待写者完成 45 WaitForSingleObject(g_hEventWriter, INFINITE); 46 47 //读者个数增加 48 EnterCriticalSection(&g_cs_writer_count); 49 g_nReaderCount++; 50 if (g_nReaderCount == 1) 51 ResetEvent(g_hEventNoReader); 52 LeaveCriticalSection(&g_cs_writer_count); 53 54 //读取文件 55 ReaderPrintf("编号为%d的读者开始读取文件...\n", GetCurrentThreadId()); 56 57 Sleep(rand() % 100); 58 59 //结束阅读,读者个数减小,空位增加 60 ReaderPrintf(" 编号为%d的读者结束读取文件\n", GetCurrentThreadId()); 61 62 //读者个数减少 63 EnterCriticalSection(&g_cs_writer_count); 64 g_nReaderCount--; 65 if (g_nReaderCount == 0) 66 SetEvent(g_hEventNoReader); 67 LeaveCriticalSection(&g_cs_writer_count); 68 69 return 0; 70 } 71 //写者线程输出函数 72 void WriterPrintf(char *pszStr) 73 { 74 EnterCriticalSection(&g_cs); 75 SetConsoleColor(FOREGROUND_GREEN); 76 printf(" %s\n", pszStr); 77 SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 78 LeaveCriticalSection(&g_cs); 79 } 80 //写者线程函数 81 unsigned int __stdcall WriterThreadFun(PVOID pM) 82 { 83 WriterPrintf("写者线程进入等待中..."); 84 //等待读文件的读者为零 85 WaitForSingleObject(g_hEventNoReader, INFINITE); 86 //标记写者正在写文件 87 ResetEvent(g_hEventWriter); 88 89 //写文件 90 WriterPrintf(" 写者开始写文件....."); 91 Sleep(rand() % 100); 92 WriterPrintf(" 写者结束写文件"); 93 94 //标记写者结束写文件 95 SetEvent(g_hEventWriter); 96 return 0; 97 } 98 99 int main() 100 { 101 printf(" 读者写者问题\n"); 102 printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n"); 103 104 //初始化事件和信号量 105 InitializeCriticalSection(&g_cs); 106 InitializeCriticalSection(&g_cs_writer_count); 107 108 //手动置位,初始已触发 109 g_hEventWriter = CreateEvent(NULL, TRUE, TRUE, NULL); 110 g_hEventNoReader = CreateEvent(NULL, FALSE, TRUE, NULL); 111 g_nReaderCount = 0; 112 113 int i; 114 HANDLE hThread[READER_NUM + 1]; 115 //先启动二个读者线程 116 for (i = 1; i <= 2; i++) 117 hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL); 118 //启动写者线程 119 hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WriterThreadFun, NULL, 0, NULL); 120 Sleep(50); 121 //最后启动其它读者结程 122 for ( ; i <= READER_NUM; i++) 123 hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL); 124 WaitForMultipleObjects(READER_NUM + 1, hThread, TRUE, INFINITE); 125 for (i = 0; i < READER_NUM + 1; i++) 126 CloseHandle(hThread[i]); 127 128 //销毁事件和信号量 129 CloseHandle(g_hEventWriter); 130 CloseHandle(g_hEventNoReader); 131 DeleteCriticalSection(&g_cs); 132 DeleteCriticalSection(&g_cs_writer_count); 133 return 0; 134 }