目录
操作系统面试书籍地址:... 1
推荐教程:http://c.biancheng.net/cpp/u/xitong/. 1
1、 进程有哪几种状态?状态转换图?以及导致转换的事件?... 1
2、 进程与线程的区别... 2
3、 进程线程通信与同步... 3
4、 生产者消费者问题... 7
5、 死锁的概念... 10
6、 导致死锁的原因... 10
7、 导致死锁的四个必要条件... 10
8、 处理死锁的四个方式... 11
9、 预防死锁的方法、避免死锁的方法、检测和恢复死锁的方法... 11
10、 进程调度算法... 12
11、 池化技术:... 13
操作系统面试书籍地址:
推荐教程:http://c.biancheng.net/cpp/u/xitong/
1、 进程有哪几种状态?状态转换图?以及导致转换的事件?... 1
、 进程有哪几种状态?状态转换图?以及导致转换的事件?
如上图所示,进程包括三种状态:就绪状态,运行状态和阻塞状态。阻塞和就绪区别:阻塞是等待CPU以外的资源,就绪等待的是CPU资源。
执行状态:进程正在处理器上运行。
就绪状态:进程已经处于准备运行的状态,即进程已经获得了除了处理器以外的所有资源,一旦的到处理器即可运行。
阻塞状态:进程正在等待某一事件的发生,如果等待某一资源为可用或等待输入输出完成。即使处理器空闲当前进程也不能执行。
就绪——执行:对就绪状态的进程,当进程调度程序按一种选定的策略从中选中一个就绪进程,为之分配了处理机后,该进程便由就绪状态变为执行状态;
执行——阻塞:正在执行的进程因发生某等待事件而无法执行,则进程由执行状态变为阻塞状态,如进程提出输入/输出请求而变成等待外部设备传输信息的状态,进程申请资源(主存空间或外部设备)得不到满足时变成等待资源状态,进程运行中出现了故障(程序出错或主存储器读写错等)变成等待干预状态等等;
阻塞——就绪:处于阻塞状态的进程,在其等待的事件已经发生,如输入/输出完成,资源得到满足或错误处理完毕时,处于等待状态的进程并不马上转入执行状态,而是先转入就绪状态,然后再由系统进程调度程序在适当的时候将该进程转为执行状态;
执行——就绪:正在执行的进程,因时间片用完而被暂停执行,或在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行而被迫让出处理机时,该进程便由执行状态转变为就绪状态。 进程与线程的区别
2、 进程与线程的区别... 2
(1)、定义
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
(3)、区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
(4)优缺点
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP(双CPU机器)机器上运行,而进程则可以跨机器迁移。
3、 进程线程通信与同步
3、 进程线程通信与同步... 3
Linux线程间通信:互斥量,信号量,条件变量
windows进程通信方式:管道,共享内存,消息队列,信号量,socket
windows线程通信的方式:临界区(CriticalSection),互斥量(Mutex),信号量(Semaphore),事件(event)。
windows间进程通信:
管道:有名管道和无名管道。有名管道克服了没有名字的限制可以用于不具有亲缘关系的进程之间。
Ø 管道是半双工的,数据只能向一个方向流动;需要双方通信时需要建立起两个管道。
Ø 只能用于父子进程或者兄弟进程之间(具有亲缘关系)
Ø 单独构成一种独立文件系统:管道对于管道两端的进程而言,就是一个文件,但是他不是普通的文件,他不属于某种文件系统,而是自理门户,单独构成一种文件系统,并且只存在于内存中。
Ø 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加正在 管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
Ø 流量管道,一种半双工的通信方式,可以双向传输。
共享内存:共享内存就是映射一段被其他进程所访问的共享内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,他是针对进程间效率低而专门设计的。他往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。HANDLE hMap = CreateFileMapping创建命名的内存映射文件对象时
消息队列:消息队列是由消息链表,存放在内核中并由消息队列标识符标示。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓存区大小受限等特点。
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。常常作为一种锁机制防止某进程正在访问共享资源时,其他进程也访问该资源主要作为进程间以及同一进程内不同线程之间的同步手段。
socket:进程间通信的机制。
windows线程通信方式:线程间的通信方式大体可以分为两类:用户模式和内核模式。
内核模式,就是利用系统内核对象的单一性来进行同步,使用时需要进行内核态和用户态的切换。用户模式就不用切换到内核态,只在用户态完成操作。
用户模式:原子操作(一个单一的全局变量),临界区
内核模式:事件、信号量、临界区
同步互斥的概念:线程同步是指线程之间具有的一种制约关系,一个线程的执行依赖另一个线程的消息。当他没有得到另一线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的进程系统资源,在各单个线程同时访问时的排他性。当有若干个线程都需要使用某一共享资源时,任何时刻值允许一个线程去使用它。
原子操作:
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
volatile int ThreadData =1;//volatile确保本条指令不会因为编译器优化而省略
void ThreadProcess()
{
for(int i=0; i<6; i++)
{
Sleep(1000);
printf("Sub ThreadTick %5d!\n",(i+1)*1000);
}
ThreadData= 0;
printf("Exit Sub Thread!\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread;
DWORD ThreadID;
hThread=CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadProcess,
NULL,
0,
&ThreadID);
while (ThreadData)
{
printf("Main Thread is waiting for Sub Thread!\n");
Sleep(600);
}
printf("Main Thread Finished! \n");
system("pause");
return 0;
}
临界区:
保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
临界区包含两个操作原语:
EnterCriticalSection()进入临界区
LeaveCriticalSection()离开临界区
EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
互斥量:
互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
互斥量包含的几个操作原语:
CreateMutex() 创建一个互斥量
OpenMutex() 打开一个互斥量
ReleaseMutex() 释放互斥量
WaitForMultipleObjects() 等待互斥量对象
信号量:
信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的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() 等待信号量
信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为每一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。
事件:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
WaitForSingleObject函数用来检测hHandle事件的信号状态,当函数的执行时间超过dwMilliseconds就返回,但如果参数dwMilliseconds为INFINITE时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到WaitForSingleObject有返回直才执行后面的代码。
下面是一个线程函数MyThreadPro()
UINTCFlushDlg::MyThreadProc( LPVOID pParam )
{
WaitForSingleObject(g_event,INFINITE);
For(;;)
return 0;
}
在这个线程函数中只有设置g_event为有信号状态时才执行下面的for循环,因为g_event是全局变量,所以我们可以在别的线程中通过g_event. SetEvent控制这个线程。
事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
信号量包含的几个操作原语:
CreateEvent() 创建一个事件
OpenEvent() 打开一个事件
SetEvent() 回置事件
WaitForSingleObject() 等待一个事件
WaitForMultipleObjects() 等待多个事件
WaitForMultipleObjects 函数原型:
WaitForMultipleObjects(
IN DWORDnCount, // 等待句柄数
IN CONSTHANDLE *lpHandles, //指向句柄数组
IN BOOLbWaitAll, //是否完全等待标志
IN DWORDdwMilliseconds //等待时间
)
参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。
事件可以实现不同进程中的线程同步操作,并且可以方便的实现多个线程的优先比较等待操作,例如写多个WaitForSingleObject来代替WaitForMultipleObjects从而使编程更加灵活。
4、 生产者消费者问题
4、 生产者消费者问题... 7
某个模块负责产生数据,这些数据由另一个模块负责处理(此处的模块是广义的,可以是类、函数、线程、进程)。产生数据的模块,就形象的成为生产者;处理数据的模块,称为消费者。该模式还包括一个缓冲区。
优点:
解耦生产者消费者互不相关;支持并发;支持忙闲不均。
缓冲区存放的数据单元考虑的问题:
关联到业务对象,必须了解业务逻辑;完整性,所谓完整性就是在传输过程中,要保证数据的完整性。要么整个单元被传输,要么完全没有传递到消费者。独立性,一个数据单元的传输不应该影响其他数据单元,假如生产者在一段时间内超过消费者的处理速度,那就会导致缓冲区不断增长,并达到上限,之后的数据单元就会被丢弃。如果数据单元相互独立那么就不会影响。颗粒度。
线程方式实现生产者消费者问题:
线程方式下生产者和消费者各自是一个线程。生产者push,消费者pop。当队列慢生产者停止push,空消费者停止pop。假如生产者和消费者频繁的进行push和pop,内存开销就比较浪费,因为内存分配(new或malloc)会有加锁开销以及用户态/核心态切换开销。
//多消费者,多缓冲区问题
//1生产者2消费者4缓冲区
#include
#include
#include
//设置控制台输出颜色
BOOL SetConsoleColor(WORDwAttributes)
{
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
return FALSE;
return SetConsoleTextAttribute(hConsole,wAttributes);
}
const int END_PRODUCE_NUMBER= 8; //生产产品个数
const int BUFFER_SIZE =4; //缓冲区个数
int g_Buffer[BUFFER_SIZE]; //缓冲池
int g_i, g_j;
//信号量与关键段
CRITICAL_SECTION g_cs; //临界区
HANDLE g_hSemaphoreBufferEmpty, g_hSemaphoreBufferFull; //存储信号量
//生产者线程函数
unsigned int __stdcall ProducerThreadFun(PVOIDpM)
{
for (int i = 1; i <= END_PRODUCE_NUMBER;i++)
{
//等待有空的缓冲区出现
WaitForSingleObject(g_hSemaphoreBufferEmpty, INFINITE); //等候空信号量
//互斥的访问缓冲区,通过临界区实现单线程独立的访问当前代码段
EnterCriticalSection(&g_cs);
g_Buffer[g_i] = i;
printf("生产者在缓冲池第%d个缓冲区中投放数据%d\n",g_i, g_Buffer[g_i]);
g_i =(g_i + 1) % BUFFER_SIZE;
LeaveCriticalSection(&g_cs);
//函数功能,增加信号量的计数如果成功,就调用信号量上的一个等待函数来减少它的计数
//并且一旦值大于零,信号量就会触发
ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);//增加满信号量
}
printf("生产者完成任务,线程结束运行\n");
return0;
}
//消费者线程函数
unsigned int __stdcall ConsumerThreadFun(PVOIDpM)
{
while (true)
{
//等待非空的缓冲区出现
WaitForSingleObject(g_hSemaphoreBufferFull, INFINITE);
//互斥的访问缓冲区
EnterCriticalSection(&g_cs);
//SetConsoleColor(FOREGROUND_GREEN);
printf(" 编号为%d的消费者从缓冲池中第%d个缓冲区取出数据%d\n", GetCurrentThreadId(), g_j,g_Buffer[g_j]);
//SetConsoleColor(FOREGROUND_RED| FOREGROUND_GREEN | FOREGROUND_BLUE);
if (g_Buffer[g_j]== END_PRODUCE_NUMBER)//结束标志
{
LeaveCriticalSection(&g_cs);
//通知其它消费者有新数据了(结束标志)
ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);
break;
}
g_j =(g_j + 1) % BUFFER_SIZE;
LeaveCriticalSection(&g_cs);
Sleep(50);//some other work to do
ReleaseSemaphore(g_hSemaphoreBufferEmpty, 1, NULL);
}
//SetConsoleColor(FOREGROUND_GREEN);
printf(" 编号为%d的消费者收到通知,线程结束运行\n",GetCurrentThreadId());
//SetConsoleColor(FOREGROUND_RED| FOREGROUND_GREEN | FOREGROUND_BLUE);
return0;
}
int main()
{
printf(" 生产者消费者问题 1生产者2消费者4缓冲区\n");
printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows) --\n\n");
InitializeCriticalSection(&g_cs); //初始化一个临界资源对象
//初始化信号量,一个记录有产品的缓冲区个数,另一个记录空缓冲区个数. 一旦值大于零,信号量就会触发(发出信号)。
//ReleaseSemaphore函数的作用是增加信号量的计数。如果成功,就调用信号量上的一个等待函数来减少它的计数
g_hSemaphoreBufferEmpty= CreateSemaphore(NULL,4, 4, NULL); //第二位、第三位设置信号量的初试计数和最大计数
g_hSemaphoreBufferFull = CreateSemaphore(NULL, 0, 4, NULL); //因此空信号量上来就被触发,生产者进行生产
g_i =0;
g_j =0;
memset(g_Buffer, 0, sizeof(g_Buffer));
const int THREADNUM =3;
HANDLE hThread[THREADNUM];
//生产者线程
hThread[0]= (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun,NULL, 0, NULL);
//消费者线程
hThread[1]= (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun,NULL, 0, NULL);
hThread[2]= (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun,NULL, 0, NULL);
WaitForMultipleObjects(THREADNUM, hThread,TRUE, INFINITE);
for (int i = 0; i < THREADNUM;i++)
CloseHandle(hThread[i]);
//销毁信号量和关键段
CloseHandle(g_hSemaphoreBufferEmpty);
CloseHandle(g_hSemaphoreBufferFull);
DeleteCriticalSection(&g_cs);
system("pause");
return0;
}
5、 死锁的概念... 10
定义:多个进程因竞争资源而造成的一种僵局(互相等待),若无外力则这些进程都将无法向前推进。实例:线程1持有资源a,线程2持有资源b,但是线程1必须也同时持有资源b才能进行下去,所以线程1等待线程2释放资源b,而线程2也必须持有资源a才能进行下去,所以线程2等待线程1释放资源a,这样就形成了循环等待的条件,都无法进行下去,这就是死锁现象。
6、 导致死锁的原因... 10
(a)系统资源不足;(b)进程推进顺序非法。
7、 导致死锁的四个必要条件... 10
n 互斥条件:在一段时间之内某资源仅为一个进程所占用。此时若有其他进程请求资源,则请求进程只能等待。
n 不剥夺条件:进程所使用的资源在未使用完毕之前不能被其他进程强行夺走。
n 请求和保持条件:进程每次申请他所需要的资源,在等待新资源的同时进程继续占有已分配到的资源。
n 循环等待条件:存在一个处于等待状态的进程集合。进程相互等待资源,形成环状路线,导致每个进程都不能获取到相应资源。
8、 处理死锁的四个方式... 11
9、 预防死锁的方法、避免死锁的方法、检测和恢复死锁的方法... 11
预防死锁:
Ø 资源一次性分配(破坏请求和保持条件)。
Ø 可剥夺资源:即当某进程新的资源未满足时候,释放已经占有的资源(破坏不可剥夺条件)。
Ø 资源有序分配:系统给每一类资源赋予一个编号,每一个进程按照编号递增的顺序请求资源,释放则相反(破坏环路等待条件);
Ø 以上几种预防死锁的策略会严重损害系统性能。因此在死锁避免的时候要施加较弱的限制,从而获得较满意的系统性能。
避免死锁
Ø 资源动态分配过程中通过银行家算法防止系统进入不安全状态。优点:银行家算法允许死锁必要条件中的互斥条件,请求和保持条件,不剥夺条件,这样同预防死锁的方法比限制条件少了,资源利用程度提高了。缺点:由于寻找安全序列,实际上增加了系统开销。
安全序列:指一个进程序列{P1,…,Pn}是安全的,即对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和。
银行家算法:保证自己的限额至少不小于一个客户的限额。
银行家算法最简单的实现:建立一个二维表格:每列数据表示每次交易各个参与者的限额。这个表格第一列数据是银行家是客户的交易限额,每发生一次交易,增加一列,同时将银行家和发生交易的客户的限额减小。直到银行家的限额小于某个客户的限额时,交易不能继续进行。否则便发生死锁。
操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。
检测死锁
Ø 通过资源分配图进行死锁的检测。通过G=(V,E),V是有向图定点的集合,包括进程节点集合P和资源节点集合R;E为有向边的集合(p,r)表示进程p申请资源r,(r,p)表示资源r被p占用。如果在一个资源分配图中,从任意节点出发,都不存在一条路径能回到自身,则系统中没有死锁。
解除死锁
Ø 剥夺资源,从其他进程剥夺足够数量的资源给死锁进程,以解除死锁状态。
Ø 撤销进程,可以直接撤销死锁进程或撤销代价最小的进程,直到有足够资源可以使用,死锁状态消除为止。代价是指优先级、运行代价、进程的重要性和价值。
Ø 进程回退,让参与死锁的进程回退到没有发生死锁前某一点处,并由此点继续执行。操作系统开销大,需要堆栈等记录进程变化,无法实现。
10、 进程调度算法... 12
周转时间 = 程序结束时间– 开始服务时间、带权周转时间= 周转时间 / 要求服务时间;
调度的基本准则包括:CPU利用率、系统吞吐量、周转时间、等待时间、响应时间。
系统吞吐量:表示单位时间内CPU完成作业的数量。
周转时间:看上边计算公式
等待时间:进程处于等待处理器状态的时间和,等待时间越长,用户满意度越低。
响应时间:用户首次请求到系统首次产生响应所用的时间。
典型调度算法:先来先服务(FCFS)、短作业优先算法(SJF)、优先级调度算法、高响应比优先调度算法、时间片轮转算法、多级反馈队列调度算法,其中SJF平均等待时间。
先来先服务:把当前处于队列之首的那个进程调度到运行状态。只考虑进程进入就绪队列的先后,不考虑它的下一个CPU周期的长短及其他因素。FCFS简单易行,是一种非抢占式策略,性能确不好。优缺点:有利于长作业而不利于短作业;有利于CPU繁忙行作业,而不利于I/O繁忙型作业。
短作业优先:
优先级调度:
高相应比优先调度算法:
时间片轮转算法:
每次调度时CPU分派给队首进程,让其执行一个时间片。时间片的长度从几个ms到几百ms。一个时间片结束时,发生时钟中断,调度程序暂停当前进程的执行,将其送到就绪队列的末尾,并通过上下文切换执行当前队首的进程。
多级反馈队列调度算法:轮转算法和优先级算法的综合发展。
设置多个就绪队列,分别赋予不同的优先级。其中,每个队列执行时间片的长度也不同,规定优先级越低则时间片越长
11、 池化技术:... 13
原因:程序中创建一个线程或者在堆上申请一块内存时,都涉及到很多系统调用,也非常耗CPU,如果程序中有很多类似的工作线程或者需要频繁的申请释放小的内存,如果没有进行优化,那么此处代码可能成为程序的瓶颈。
池化技术分类:进程/线程池、内存池、连接池、对象池等等。
线程池:
线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。
线程池的实现:
线程池管理器:用于创建并管理线程池,包括创建线程、销毁线程池、添加新任务。
工作线程:线程池中线程,在没有任务时处于等待状态,可以循环的执行任务。
任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行。主要规定了任务的入口任务完成后的收尾工作,任务执行状态等。
任务队列(请求):用于存放没有处理的任务,提供一种缓冲机制。
结果队列:用于存储请求执行后返回的结果
(1)线程池管理器,通过添加请求的方法向请求队列添加请求(请求需要事先实现请求接口,即传递工作函数、参数、结果处理函数以及异常处理函数)。
(2)初始化一定数量的工作线程,这些线程通过轮训的方式不断查看请求队列,只要有请求在则会提出请求并执行。
(3)然后线程池管理器调用方法查看结果队列是否有值,如果有值则取出,调用结果处理函数执行。
通过以上方法,这个系统的核心资源在于请求队列和结果队列,工作线程通过轮训获得任务,主线程通过查看结果队列,获得执行结果。对这个队列的设计要实现线程同步,一定阻塞和超时机制的设计,防止不断轮序导致过多cpu开销。
线程池的实现:我们实现的通用线程池框架由五个重要部分组成
CThreadManage,CThreadPool,CThread,CJob,CWorkerThread,除此之外框架中还包括线程同步使用的类CThreadMutex和CCondition。
n CThreadManage是线程池与用户的直接接口,其屏蔽了内部的具体实现。
n CThreadPool是线程池类,其负责保存线程,释放线程以及调度线程。
n CThread是Linux中线程的包装,其封装了Linux线程最经常使用的属性和方法,它也是一个抽象类,是所有线程类的基类,具有一个接口Run。
n CJob是所有的任务的基类,其提供一个接口Run,所有的任务类都必须从该类继承,同时实现Run方法。该方法中实现具体的任务逻辑。
n CWorkerThread是实际被调度和执行的线程类,其从CThread继承而来,实现了CThread中的Run方法。
n CThreadMutex用于线程之间的互斥。
n CCondition则是条件变量的封装,用于线程之间的同步。
线程池的时序很简单,如下图所示。CThreadManage直接跟客户端打交道,其接受需要创建的线程初始个数,并接受客户端提交的任务。这儿的任务是具体的非抽象的任务。CThreadManage的内部实际上调用的都是CThreadPool的相关操作。CThreadPool创建具体的线程,并把客户端提交的任务分发给CWorkerThread,CWorkerThread实际执行具体的任务。
线程池主要用于:
1、需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
2、对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。
内存池(Memorypool):
目的:提出解决方案管理程序中内存的使用,提高内存的使用效率。
原理:预先分配足够大的内存,形成一个初步的“内存池”。分配内存,就是用户请求内存时,会返回内存池中一块空闲的内存,并将其标识为已经使用。释放内存时,不是真正的delete或者free而是把内存放回内存池的过程,同时把标志位设置空闲。最后应用程序结束时,把内存池销毁。
优点:减少了内存碎片的产生,因为创建内存池时,分配的都是一块一块比较完整的内存块。
缺点:提高了内存的使用效率。这个可以从分配和释放内存看出,因为每次释放没有调用系统函数,而是复用内存池中的内存。
连接池:
数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
最小连接数是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费;
最大连接数是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求将被加入到等待队列中,这会影响之后的数据库操作。
对象池:
12、 避免在程序的生命周期中创建和删除大量的对象。如果知道程序需要同一类型的对象,而且对象的生命周期都很短,就可以为这些对象创建一个池进行缓存。