bcd
bd
请比较从内存中完全销毁Windows线程和Linux线程的方法
Windows上的线程销毁是随其线程main函数的返回,在内存中自动销毁的。但是linux的线程销毁必须经过pthread_join函数或者pthread_detach函数的响应才能在内存空间中完全销毁
通过线程创建过程解释内核对象、线程、句柄之间的关系
线程也属于操作系统的资源,因此会伴随着内核对象的创建,并为了引用内核对象而返回句柄。整理的话,可以通过句柄区分内核对象,通过内核对象区分线程
√ × ×
请解释"auto-reset模式"和"manual-reset模式"的内核对象。区分二者的内核对象特征是什么?
WaitForSingleObject
函数针对单个内核对象验证signaled状态时,由于发生事件(变为signaled状态)返回时,有时会把相应内核对象再次改为non-signaled状态。这种可以再次进入non-signaled状态的内核对象称为"auto-reset模式"的内核对象,而不会自动跳转到non-signaled状态的内核对象称为"manual-reset模式"的内核对象。
c
× √ × √
本章示例SyncSema_win.c的Read函数中,退出临界区需要较长时间,请给出解决方案并实现
对可能超过一定时间处于阻塞状态的函数,如scanf
函数的响应,应尽量不纳入临界区
unsigned WINAPI Read(void * arg)
{
int i, rdData;
for(i=0; i<5; i++)
{
fputs("Input num: ", stdout);
scanf("%d", &rdData);
WaitForSingleObject(semTwo, INFINITE);
num=rdData;
ReleaseSemaphore(semOne, 1, NULL);
}
return 0;
}
更改程序,并运行结果
#include
#include
#include
#define STR_LEN 100
unsigned WINAPI NumberOfA(void *arg);
unsigned WINAPI NumberOfOthers(void *arg);
static char str[STR_LEN];
static HANDLE hSema;
int main(int argc, char *argv[])
{
HANDLE hThread1, hThread2;
hSema=CreateSemaphore(NULL, 0, 2, NULL);
hThread1=(HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
hThread2=(HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);
fputs("Input string: ", stdout);
fgets(str, STR_LEN, stdin);
ReleaseSemaphore(hSema, 2, NULL);
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
ResetEvent(hSema);
CloseHandle(hSema);
return 0;
}
unsigned WINAPI NumberOfA(void *arg)
{
int i, cnt=0;
WaitForSingleObject(hSema, INFINITE);
for(i=0; str[i]!=0; i++)
{
if(str[i]=='A')
cnt++;
}
printf("Num of A: %d \n", cnt);
return 0;
}
unsigned WINAPI NumberOfOthers(void *arg)
{
int i, cnt=0;
WaitForSingleObject(hSema, INFINITE);
for(i=0; str[i]!=0; i++)
{
if(str[i]!='A')
cnt++;
}
printf("Num of others: %d \n", cnt-1);
return 0;
}
结合send & recv 函数解释同步和异步方式的I/O。并请说明同步I/O的缺点,以及怎样通过异步I/O进行解决
同步I/O是指数据发送的时间点和函数返回的时间一致的I/O方式。即,send函数返还的时间是数据传输完毕的时间,recv函数返还的时间是数据接收完毕的时间。但是异步I/O是指I/O函数的返回时刻与数据收发的完成时刻不一致。同步I/O的缺点是,直到输入输出完成为止,都处于阻塞状态。在这段时间里,CPU实际上是没有被利用的,但由于阻塞状态,无法进行其他工作。从这一点看,异步I/O对CPU的使用效率优于同步I/O
异步I/O并不是所有状况下的最佳选择。它具有哪些缺点?何种情况下同步I/O更优?可以参考异步I/O相关源代码,亦可结合线程进行说明
异步I/O有优点也有缺点,缺点在于要在完成输入输出以后去确认。如果服务器的服务非常简单,回复所需的数据量很小,就会很不方便。特别是在每一个用户都生成一个线程的服务器中,没有必要一定使用异步I/O
全√
请从源代码的角度说明select函数和WSAEventSelect函数再使用上的差异
使用select函数时,要循环将观察的文件描述符传到select函数中。而如果使用WSAEventSelect
函数,观察的对象会被操作系统记录下俩,无需每次都注册。而且,如果是用select函数,则到事件发生为止,应保持阻塞状态;而如果使用WSAEventSelect
函数,到事件发生为止,都不应该进入阻塞。
第17章的epoll可以在条件触发和边缘触发这2中方式下工作。那种方式更适合异步I/O模型?为什么?请概括说明
边缘触发很好地配合异步I/O模型。边缘触发模式并不会因为输入缓冲中有数据而发起消息。即在边缘触发中,对异步的存取发生过程,不会发起消息,而只注册新的输入事件。但是在条件触发中,当输入缓冲中有有数据时,仍然会发起消息
Linux中的epoll同样属于异步I/O模型。请说明原因
epoll方式将观察的对象的连接过程和事件发生过程分离成两种处理模式。因此,可以在事件发生后,在希望的时间确认活动是否发生。因此epoll可以说是一种异步I/O模式
如何获取WSAWaitForMultipleEvents可以监视的最大句柄数?请编写代码读取该值
获取WSA_MAXIMUM_WAIT_EVENTS的值即可
为何异步通知I/O模型中的事件对象必须是manual-reset模式
事件的发生,需要由WSAWaitForMultipleEvents
函数调用寻找相应的句柄调用执行的;首先通过调用WSAWaitForMultipleEvents
函数寻找事件发生的句柄数目,然后遍历找到具体发生的事件进行处理。如果不是manual-reset模式,就会自动转换为signaled状态,无法用第二次的WSAWaitForMultipleEvents
函数进行操作处理
代码
/*****************************AysnNotiChatServ.c*****************************/
#include
#include
#include
#define BUF_SIZE 100
void SendMsg(SOCKET clntSocks[], int clntCnt, char * msg, int len);
void CompressSockets(SOCKET hSockArr[], int idx, int total);
void CompressEvents(WSAEVENT hEventArr[], int idx, int total);
void ErrorHandling(char *msg);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hServSock, hClntSock;
SOCKADDR_IN servAdr, clntAdr;
SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT newEvent;
WSANETWORKEVENTS netEvents;
int numOfClntSock=0;
int strLen, i;
int posInfo, startIdx;
int clntAdrLen;
char msg[BUF_SIZE];
if(argc!=2) {
printf("Usage: %s \n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hServSock=socket(PF_INET, SOCK_STREAM, 0);
servAdr.sin_family=AF_INET;
servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
servAdr.sin_port=htons(atoi(argv[1]));
if(bind(hServSock, (SOCKADDR*) &servAdr, sizeof(servAdr))==SOCKET_ERROR)
ErrorHandling("bind() error");
if(listen(hServSock, 5)==SOCKET_ERROR)
ErrorHandling("listen() error");
newEvent=WSACreateEvent();
if(WSAEventSelect(hServSock, newEvent, FD_ACCEPT)==SOCKET_ERROR)
ErrorHandling("WSAEventSelect() error");
hSockArr[numOfClntSock]=hServSock;
hEventArr[numOfClntSock]=newEvent;
numOfClntSock++;
while(1)
{
posInfo=WSAWaitForMultipleEvents(
numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE);
startIdx=posInfo-WSA_WAIT_EVENT_0;
for(i=startIdx; i
异步通知I/O模型和重叠I/O模型在异步处理方面有哪些区别?
在异步I/O模型下,以异步的方式处理IO事件发生的过程
在重叠I/O模型下,还需要异步确认I/O完成的过程
请分析非阻塞I/O、异步I/O、重叠I/O之间的关系
异步I/O意味着异步处理,确认I/O完成的过程。为了实现这一过程,I/O必须在非阻塞模式下运行。当I/O以非阻塞模式运行并且I/O以异步方式进行时,I/O会重叠
阅读下面代码,指出问题并给出解决方案
若accpet函数返回失败,WSARecv传入的overlapped会报错,因为overlapped事先没有任何传入。若accept返回成功,因为所有的OVERLAPPED结构都相同,可以使用同一个OVERLAPPED结构体变量给多个WSARecv函数调用
请从源代码角度说明调用WSASend函数后如何验证进入Pending状态
WSASend函数再返回SOCKET_ERROR的状态下,利用WSAGetLastError函数是否返回WSA_IO_PENDING来验证
线程的"alertable wait状态"的含义是什么?说出能使线程进入这种状态的两个函数
所谓"alertable wait状态"是指等待接收操作系统信息的状态,在接下来的函数调用中,线程会进入alertable wait状态
完成端口对象将分配多个线程用于处理I/O。如何创建这些线程?如何分配?请从源码级别进行说明
Completion Port
对象所分摊的线程由程序员创建。这些线程为了确认I/O的完成会调用GetQueuedCompletionStatus
函数。虽然任何线程都能调用GetQueuedCompletionStatus
函数,但实际得到I/O完成信息的线程数不会超过调用CreateIoCompletionPort
函数时指定的最大线程数。
CreateIoCompletionPort
函数与其他函数不同,提供2种功能。请问是哪2种?
Completion Port对象的产生,CP对象与套接字进行连接
完成端口对象和套接字之间的连接意味着什么?如何连接?
首先通过CreateIoCompletionPort
生成CP对象。之后再次使用CreateIoCompletionPort
函数将CP对象与套接字进行连接。
cd
√ √ ×
代码
#include
#include
#include
#include
#include
#define BUF_SIZE 100
#define MAX_CLNT 256
#define READ 3
#define WRITE 5
typedef struct // socket info
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
typedef struct // buffer info
{
OVERLAPPED overlapped;
WSABUF wsaBuf;
char buffer[BUF_SIZE];
} PER_IO_DATA, *LPPER_IO_DATA;
DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void SendMsg(char * msg, DWORD len);
void ErrorHandling(char *message);
int clntCnt=0;
SOCKET clntSocks[MAX_CLNT];
int main(int argc, char* argv[])
{
WSADATA wsaData;
HANDLE hComPort;
SYSTEM_INFO sysInfo;
LPPER_IO_DATA ioInfo;
LPPER_HANDLE_DATA handleInfo;
SOCKET hServSock;
SOCKADDR_IN servAdr;
int recvBytes, i, flags=0;
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hComPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
GetSystemInfo(&sysInfo);
for(i=0; ihClntSock=hClntSock;
memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);
CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);
ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len=BUF_SIZE;
ioInfo->wsaBuf.buf=ioInfo->buffer;
WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf),
1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);
}
return 0;
}
DWORD WINAPI EchoThreadMain(LPVOID pComPort)
{
HANDLE hComPort=(HANDLE)pComPort;
SOCKET sock;
DWORD bytesTrans;
LPPER_HANDLE_DATA handleInfo;
LPPER_IO_DATA ioInfo;
DWORD flags=0;
int i;
while(1)
{
GetQueuedCompletionStatus(hComPort, &bytesTrans,
(LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
sock=handleInfo->hClntSock;
puts("message received!");
if(bytesTrans==0) // EOF
{
for(i=0; ibuffer, bytesTrans);
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
WSARecv(sock, &(ioInfo->wsaBuf),
1, NULL, &flags, &(ioInfo->overlapped), NULL);
}
return 0;
}
void SendMsg(char * msg, DWORD len) // send to all
{
int i;
for(i=0; i
abe
a
IOCP和epoll是可以保证高性能的典型服务器端模型,但如果在基于HTTP协议的Web服务器端使用这些模型,则无法保证一定能得到高性能。请说明原因
IOCP和epoll都是可以管理两个以上socket的服务器模型。即在称为观察对象的端口中,感知与IO相关的事件发生的端口,并处理相关的I/O服务器模式。如实对网络服务器来说,并不要管理两个以上的socket。因为只要完成一次请求和回答的过程,连接就会结束。因此,用IOCP和epoll提高性能,有一定的局限性