好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

多进程多线程并发服务器课件.ppt

69页
  • 卖家[上传人]:壹****1
  • 文档编号:577043298
  • 上传时间:2024-08-21
  • 文档格式:PPT
  • 文档大小:462.51KB
  • / 69 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • 第5章 并发服务器 目录n服务器分类n进程与线程n多进程服务器n多线程服务器并发服务器 服务器分类n按连接类型分类n面向连接的服务器(如tcp)n面向无连接的服务器(如udp)n按处理方式分类n迭代服务器n并发服务器 迭代服务器 vs. 并发服务器绑定地址监听连接接收连接处理连接断开连接接收请求处理请求返回响应绑定地址监听连接接收连接创建子进程关闭连接套接字处理连接关闭连接套接字终止子进程关闭监听套接字服务器主进程服务器子进程TCP迭代服务器TCP并发服务器 “进程”基本概念n进程定义了一个计算的基本单元,可以认为是一个程序的一次运行它是一个动态实体,是独立的任务它拥有独立的地址空间、执行堆栈、文件描述符等n每个进程拥有独立的地址空间,进程间正常情况下,互不影响,一个进程的崩溃不会造成其他进程的崩溃n当进程间共享某一资源时,需注意两个问题:同步问题和通信问题 创建进程#include #include pid_t fork(void)返回:父进程中返回子进程的进程返回:父进程中返回子进程的进程ID, 子进程返回子进程返回0,, -1-出错-出错nfork后,子进程和父进程继续执行后,子进程和父进程继续执行fork()函数后的指令。

      函数后的指令子进程是父进程的副本子进程拥有父进程的数据空间、子进程是父进程的副本子进程拥有父进程的数据空间、堆栈的副本但父、子进程并不共享这些存储空间部分堆栈的副本但父、子进程并不共享这些存储空间部分如果代码段是只读的,则父子进程共享代码段如果父子如果代码段是只读的,则父子进程共享代码段如果父子进程同时对同一文件描述字操作,而又没有任何形式的同进程同时对同一文件描述字操作,而又没有任何形式的同步,则会出现混乱的状况;步,则会出现混乱的状况;n父进程中调用父进程中调用fork之前打开的所有描述字在函数之前打开的所有描述字在函数fork返回返回之后子进程会得到一个副本之后子进程会得到一个副本fork后,父子进程均需要将后,父子进程均需要将自己不使用的描述字关闭,有两方面的原因:(自己不使用的描述字关闭,有两方面的原因:(1)以免)以免出现不同步的情况;(出现不同步的情况;(2))最后能正常关闭描述字最后能正常关闭描述字 #include#includemain(){ int i, sum; sum = 0; for(i =0;i<=2;i++) { sum = sum +i; printf(“i=%d\n",i); } printf("sum = %d\n",sum);} int main(void){ pid_t pid; int status; if ((pid = fork()) == 0) { sleep(2); printf("child running.\n"); printf("child sleeping.\n"); sleep(2); printf("child dead.\n"); exit(0); }else if ( pid > 0) { printf("parent running .\n"); printf("parent exit\n"); exit(0); } else { printf("fork error.\n"); exit(1); }} 创建进程(cont.)#include #include pid_t vfork(void);n在在BSD3.0中开始出现,主要为了解决中开始出现,主要为了解决fork昂贵的开销。

      它昂贵的开销它是完全共享的创建,新老进程共享同样的资源,完全没有拷是完全共享的创建,新老进程共享同样的资源,完全没有拷贝n两者的基本区别在于当使用两者的基本区别在于当使用vfork()创建新进程时,父进程创建新进程时,父进程将被暂时阻塞,而子进程则可以借用父进程的地址空间这将被暂时阻塞,而子进程则可以借用父进程的地址空间这个奇特状态将持续直到子进程退出或调用个奇特状态将持续直到子进程退出或调用execve()函数,函数,至此父进程才继续执行至此父进程才继续执行 int int main(voidmain(void) {) { pid_tpid_t pidpid; ; int status; int status; if (( if ((pidpid = = vforkvfork()) == 0){()) == 0){ sleep(2); sleep(2); printf("childprintf("child running.\n"); running.\n"); printf("childprintf("child sleeping.\n"); sleeping.\n"); sleep(2); sleep(2); printf("childprintf("child dead.\n"); dead.\n"); exit(0); exit(0); }else if ( }else if ( pidpid > 0) > 0) { { printf("parentprintf("parent running .\n"); running .\n"); printf("parentprintf("parent exit\n"); exit\n"); exit(0); exit(0); } else { } else { printf("forkprintf("fork error.\n"); error.\n"); exit(1); exit(1); } }} } 运行的结果: 终止进程终止进程n进程的终止存在两个可能:进程的终止存在两个可能:n父进程先于子进程终止(父进程先于子进程终止(init进程领养)进程领养)n子进程先于主进程终止子进程先于主进程终止n对于后者,系统内核为子进程保留一定的状态对于后者,系统内核为子进程保留一定的状态信息:进程信息:进程ID、、终止状态终止状态、、CPU时间等;当父时间等;当父进程调用进程调用wait或或waitpid函数时,获取这些信函数时,获取这些信息;(什么叫息;(什么叫“僵尸进程僵尸进程”?)?)n当子进程正常或异常终止时,系统内核向其父当子进程正常或异常终止时,系统内核向其父进程发送进程发送SIGCHLD信号;缺省情况下,父进信号;缺省情况下,父进程忽略该信号,或者提供一个该信号发生时即程忽略该信号,或者提供一个该信号发生时即被调用的函数。

      被调用的函数 终止进程(续)终止进程(续)#include void exit(int status);n本函数终止调用进程关闭所有子进程打开的描述本函数终止调用进程关闭所有子进程打开的描述符,向父进程发送符,向父进程发送SIGCHLD信号,并返回状态信号,并返回状态 获取子进程终止信息获取子进程终止信息#include #include pid_t wait(int *stat_loc); 返回:终止子进程的返回:终止子进程的ID--成功;成功;-1-出错;-出错;stat_loc存储存储子进程的终止状态(一个整数);子进程的终止状态(一个整数);n如果没有终止的子进程,但是有一个或多个正在执如果没有终止的子进程,但是有一个或多个正在执行的子进程,则该函数将堵塞,直到有一个子进程行的子进程,则该函数将堵塞,直到有一个子进程终止或者终止或者wait被信号中断时,被信号中断时,wait返回n当调用该系统调用时,如果有一个子进程已经终止,当调用该系统调用时,如果有一个子进程已经终止,则该系统调用立即返回,并释放子进程所有资源。

      则该系统调用立即返回,并释放子进程所有资源 获取子进程终止信息获取子进程终止信息在SIGCHLD信号处理函数中使用wait()函数可能会出现一个问题SIGCHLD服务器父进程服务器父进程服务器子进程服务器子进程服务器子进程服务器子进程服务器子进程服务器子进程客户客户FINFINFINSIGCHLDSIGCHLD由于由于linux信号不排队,在信号不排队,在SIGCHLD信号同时到来后,信信号同时到来后,信号处理程序中调用了号处理程序中调用了wait函数,其只执行一次,这样将留函数,其只执行一次,这样将留下下2个僵尸进程可以使用个僵尸进程可以使用waitpid函数解决这个问题函数解决这个问题 获取子进程终止信息获取子进程终止信息pid_t waitpid(pid_t pid, int *stat_loc, int options); 返回:终止子进程的返回:终止子进程的ID--成功;成功;-1-出错;-出错;stat_loc存储存储子进程的终止状态;子进程的终止状态;n当当pid=-1,option=0时,该函数等同于时,该函数等同于wait,,否则否则由参数由参数pid和和option共同决定函数行为,其中共同决定函数行为,其中pid参参数意义如下:数意义如下:n-1:要求知道任何一个子进程的返回状态(等待第一:要求知道任何一个子进程的返回状态(等待第一个终止的子进程);个终止的子进程);n>0:要求知道进程号为:要求知道进程号为pid的子进程的状态;的子进程的状态;n<-1:  wait for any child process whose process group ID is equal to the absolute value of pid.nOptions最常用的选项是最常用的选项是WNOHANG,它通知它通知内核在没有已终止进程时不要堵塞。

      内核在没有已终止进程时不要堵塞 获取子进程终止信息(获取子进程终止信息(cont.))n调用调用wait或或waitpid函数时,正常情况下,函数时,正常情况下,可能会有以下几种情况:可能会有以下几种情况:n阻塞(如果其所有子进程都还在运行);阻塞(如果其所有子进程都还在运行);n获得子进程的终止状态并立即返回(如果获得子进程的终止状态并立即返回(如果一个子进程已终止,正等待父进程存取其一个子进程已终止,正等待父进程存取其终止状态);终止状态);n出错立即返回(如果它没有任何子进程)出错立即返回(如果它没有任何子进程) pid_t pid;int stat;while((pid=waitpid(-1,&stat,WNOHANG))>0) printf(“child %d terminated\n”,pid); waitpid函数用法pid_t pid;if ((pid=fork()) > 0) /* parent process */{int child_status; waitpid(pid, &child_status, 0);}else if ( pid == 0 ) { /* child process */exit(0);}else { /* fork error */printf(“fork error.\n”);exit(1);} 程序例子:程序例子:#include #include #include int main(void){ pid_t pid; int status; if ((pid = fork()) == 0) { printf("child running.\n"); sleep(1); printf("child sleeping.\n"); printf("child dead.\n"); exit(0); } else if ( pid > 0) { printf("parent running .\n"); waitpid(pid, &status,0); printf("parent is running\n"); printf("parent exit\n"); } else { printf("fork error.\n"); exit(1); }} 多进程并发服务器状态图服务器客户connect()函数listenfd客户/服务器状态图(调用accept函数时)连接请求 多进程并发服务器状态图(cont.)服务器服务器客户客户connect()函数listenfd客户/服务器状态图(调用accept函数后)connfd连接建立 多进程并发服务器状态图(cont.)服务器(父进程)服务器(父进程)客户客户connect()函数函数listenfd客户/服务器状态图(调用fork函数后)connfd连接建立连接建立服务器(子进程)listenfdconnfdfork()函数 多进程并发服务器状态图(cont.)服务器(父进程)服务器(父进程)客户客户connect()函数函数listenfd客户/服务器状态图(父进程关闭连接套接字,子进程关闭监听套接字)连接建立服务器(子进程)服务器(子进程)connfd 多进程并发服务器模板……int main(void){int listenfd, connfd;pid_t pid;intBACKLOG = 5;if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {perror(“Create socket failed.”);exit(1);} bind(listenfd, …);listen(listenfd, BACKLOG);while(1) {if ((connfd = accept(sockfd, NULL, NULL)) == -1) {perror(“Accept error.”); exit(1); } 多进程并发服务器模板if((pid = fork() ) > 0){ /*parent process */ close(connfd); ……. continue;}else if (pid == 0){ /*child process */ close(lisetenfd); ……. exit(0);}else{ printf(“fork error\n”); exit(1); } }} 一点说明n从以上模板看出,产生新的子进程后,父进程要关闭连接套接字,而子进程要关闭监听套接字,主要原因是:n关闭不需要的套接字可节省系统资源,同时可避免父子进程共享这些套接字可能带来的不可预计的后果;n另一个更重要的原因,是为了正确地关闭连接。

      和文件描述符一样,每个套接字描述符都有一个“引用计数”当fork函数返回后,listenfd和connfd的引用计数变为2,而系统只有在某描述符的“引用计数”为0时,才真正关闭该描述符 多进程并发服务器实例n该实例包括服务器程序和客户程序,具体功能如该实例包括服务器程序和客户程序,具体功能如下:下:n服务器等待客户连接请求,连接成功后显示客户地址,服务器等待客户连接请求,连接成功后显示客户地址,并接收该客户的名字并显示,然后接收来自客户的信息并接收该客户的名字并显示,然后接收来自客户的信息(字符串)并显示,然后(字符串)并显示,然后将该字符串反转,将该字符串反转,并将结果送并将结果送回客户,之后,继续等待接收该客户的信息直至该客户回客户,之后,继续等待接收该客户的信息直至该客户关闭连接要求服务器具有同时处理多个客户的能力关闭连接要求服务器具有同时处理多个客户的能力n客户首先与服务器连接,接着接收用户输入客户的名字,客户首先与服务器连接,接着接收用户输入客户的名字,并将该名字发送给服务器,接收用户输入的字符串,发并将该名字发送给服务器,接收用户输入的字符串,发送给服务器,接收服务器返回的经处理后的字符串,并送给服务器,接收服务器返回的经处理后的字符串,并显示之。

      之后,继续等待用户输入直至用户输入显示之之后,继续等待用户输入直至用户输入Ctrl+C,终止连接并退出终止连接并退出 多进程并发服务器-服务器#include ……#define PORT1234#define BACKLOG10#define MAXDATASIZE1000void process_cli(int connectfd, struct sockaddr_in client);int main(void){int listenfd, connectfd;pid_tpid;struct sockaddr_intserver, client;int sin_size; /* Create TCP Socket */…… if((listenfd=socket(AF_INET, SOCK_STREAM, 0))==-1) { perror("Create socket failed"); exit(-1); }int opt = SO_REUSEADDR;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(PORT);server.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(listenfd, (struct sockaddr *)&server, serrver))==-1) { perror("Bind error"); exit(-1); }if (listen(listenfd, BACKLOG) == -1) { perror("listen error"); exit(-1); } sin_size = sizeof(struct sockaddr_in);while(1) { if((connectfd = accept(listenfd, (struct sockaddr *)&client, &sin_size)) == -1) { perror("accept error"); exit(-1); }if ((pid=fork())>0) { /* parent process */ close(connectfd); continue; }else if (pid==0) { /*child process*/ close(listenfd); process_cli(connectfd, client); exit(0); }else { printf("fork error\n"); exit(0); }}/*while()*/close(listenfd); /* close listenfd */ } 多进程并发服务器-服务器void process_cli(int connectfd, struct sockaddr_in client) {int num;char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];printf(“You got a connection from %s.\n”, inet_ntoa(client.sin_addr));num = recv(connectfd, cli_name, MAXDATASIZE, 0);if (num == 0) {close(connectfd);printf(“cllient disconnected.\n”);return;}cli_name[num] = ‘\0’;printf(“Client name is %s.\n”,cli_name);: 多进程并发服务器-服务器while (num = recv(connectfd, recvbuf, MAXDATASIZE,0) {recvbuf[num] = ‘\0’;printf(“Received client (%s) message: %s\n”, cli_name, recvbuf);for(int i=0; i < num ; i++)sendbuf[i] = recvbuf[num-i-1];sendbuf[i] = ‘\0’;send(connectfd, sendbuf, strlen(sendbuf), 0);}close(connectfd);} 多进程并发服务器-运行结果[root@sheng sheng]# ./multiproserverThe server is runningYou got a connection from 222.18.113.153.Client name is shengReceived client(sheng)message:how a yYou got a connection from 127.0.0.1.Client name is limingReceived client(liming)message:abcdReceived client(sheng)message:12345n可以看出,服务器能同时为多个客户服务。

      可以看出,服务器能同时为多个客户服务可以思考如果采用迭代服务器,结果如何?)(可以思考如果采用迭代服务器,结果如何?) 多个客户同时请求处理 多进程服务器的问题多进程服务器的问题n传统的网络服务器程序大都在新的连接到达时,传统的网络服务器程序大都在新的连接到达时,fork一个子进程来处理虽然这种模式很多年使用一个子进程来处理虽然这种模式很多年使用得很好,但得很好,但fork有一些问题:有一些问题:nfork是昂贵的是昂贵的fork时需要复制父进程的所有资源,包时需要复制父进程的所有资源,包括内存映象、描述字等;目前的实现使用了一种写时拷括内存映象、描述字等;目前的实现使用了一种写时拷贝(贝(copy-on-write))技术,可有效避免昂贵的复制问技术,可有效避免昂贵的复制问题,但题,但fork仍然是昂贵的仍然是昂贵的;nfork子进程后,父子进程间、兄弟进程间的通信需要进子进程后,父子进程间、兄弟进程间的通信需要进程间通信程间通信(IPC)机制,给通信带来了困难;机制,给通信带来了困难;n多进程在一定程度上仍然不能有效地利用系统资源多进程在一定程度上仍然不能有效地利用系统资源;n系统中进程个数也有限制。

      系统中进程个数也有限制 “线程线程”基本概念基本概念n线程是进程内的独立执行实体和调度单元,又称为线程是进程内的独立执行实体和调度单元,又称为“轻量级轻量级”进程(进程(lightwight process););创建线程比进程快创建线程比进程快10~100倍一个进程内的所有线程共享相同的内存空间、倍一个进程内的所有线程共享相同的内存空间、全局变量等信息(这种机制又带来了同步问题)而且它们全局变量等信息(这种机制又带来了同步问题)而且它们还共享以下信息:还共享以下信息: 共享信息共享信息 私有信息私有信息n进程指令进程指令 线程线程IDn大多数数据大多数数据 寄存器集合(包括程序计数器和栈指针)寄存器集合(包括程序计数器和栈指针)n打开的文件描述字打开的文件描述字栈(用于存放局部变量等)栈(用于存放局部变量等)n信号处理程序和信号处置信号处理程序和信号处置errnon当前工作目录当前工作目录信号掩码信号掩码n用户用户ID和组和组ID优先级优先级 线程调用函数(1)#include int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg); 返回:成功时为返回:成功时为0;出错时为正的;出错时为正的Exxx值值n当一个程序开始运行时,系统会创建一个初始线程或主线程当一个程序开始运行时,系统会创建一个初始线程或主线程的单个线程。

      额外线程由上述函数创建;的单个线程额外线程由上述函数创建;n新线程由线程新线程由线程id标识:标识:tid,,新线程的属性新线程的属性attr包括:优先级、包括:优先级、初始栈大小、是否应该是守护线程等等线程的执行函数和初始栈大小、是否应该是守护线程等等线程的执行函数和调用参数分别是:调用参数分别是:func和和arg;;n由于线程的执行函数的参数和返回值类型均为由于线程的执行函数的参数和返回值类型均为void *,,因此因此可传递和返回指向任何类型的指针;可传递和返回指向任何类型的指针;n常见的返回错误值:常见的返回错误值:EAGAIN:超过了系统线程数目的限制超过了系统线程数目的限制ENOMEN:没有足够的内存产生新的线程没有足够的内存产生新的线程EINVAL:无效的属性:无效的属性attr值 例:创建一个线程#include #include pthread_t tid;void *ex(){ printf("this is a thread");}main(){ pthread_create(&tid,NULL,ex,NULL);} 线程函数调用(线程函数调用(2))#inlcude int pthread_join(pthread_t tid, void **status); 返回:成功时为返回:成功时为0;出错时为正的;出错时为正的Exxx值,不设值,不设置置errorn该函数类似与该函数类似与waitpid函数,但必须指定等函数,但必须指定等待线程的待线程的ID,该函数不能等待任意一个线程,该函数不能等待任意一个线程结束;结束;n被等待线程必须是当前进程的成员,并且不被等待线程必须是当前进程的成员,并且不是是分离的线程分离的线程和和守护线程守护线程。

      pthread_t pthread_self(void); 返回:调用线程的线程返回:调用线程的线程id; 线程函数调用(3)#include int pthread_detach(pthread_t tid) 返回:成功时为返回:成功时为0;出错时为正;出错时为正Exxx值;值;n线程或者是可汇合的(线程或者是可汇合的(joinable)(默认),或者是)(默认),或者是脱离的(脱离的(detached)当可汇合的线程终止时,其当可汇合的线程终止时,其线程线程id和退出状态将保留,直到另外一个线程调用和退出状态将保留,直到另外一个线程调用pthread_join脱离的线程则像守护进程,当它终脱离的线程则像守护进程,当它终止时,释放所有资源,我们不能等待它终止止时,释放所有资源,我们不能等待它终止n该函数将指定的线程变为脱离的该函数将指定的线程变为脱离的 pthread_detach((pthread_self())()); 线程函数调用(线程函数调用(4))#include void pthread_exit(void *status); 无返回值;无返回值;n如果线程为可汇合的,将保留线程如果线程为可汇合的,将保留线程id和退出状态供和退出状态供pthread_join()函数调用()函数调用;n指针指针status:指向线程的退出状态。

      不能指向一个局部:指向线程的退出状态不能指向一个局部变量,因为线程终止时其所有的局部变量将被撤销;变量,因为线程终止时其所有的局部变量将被撤销;n还有其他两种方法可使线程终止还有其他两种方法可使线程终止n启动线程的函数(启动线程的函数(pthread_create的第的第3个参数)个参数)返回其返回值便是线程的终止状态;返回其返回值便是线程的终止状态;n如果进程的如果进程的main函数返回,或者当前进程中,任一函数返回,或者当前进程中,任一线程调用了线程调用了exit()函数,将终止该进程中所有线程函数,将终止该进程中所有线程 线程函数调用(线程函数调用(5))##include int pthread_once(pthread_once_t *once_control, void (*init_routine) (void)) 成功返回成功返回0,否则返回错,否则返回错误码误码 n如果本函数中,如果本函数中,once_control变量使用的初值为变量使用的初值为PTHREAD_ONCE_INIT,可保证,可保证init_routine()函数在函数在本进程本进程本进程本进程执行序列中仅执行一次。

      执行序列中仅执行一次n一般在一般在init_routine函数中完成一些初始化工作函数中完成一些初始化工作 nLinuxThreads使用互斥锁和条件变量保证由使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而指定的函数执行且仅执行一次,而once_control则表征是否执行过则表征是否执行过 pthread_once函数例子函数例子#include #include pthread_once_t once=PTHREAD_ONCE_INIT;void once_run(void){printf("once_run in thread %d\n",pthread_self());}void * child1(void *arg){int tid=pthread_self();printf("thread %d enter\n",tid);pthread_once(&once,once_run);printf("thread %d returns\n",tid);pthread_exit(NULL);} void * child2(void *arg){ int tid=pthread_self(); printf("thread %d enter\n",tid); pthread_once(&once,once_run); printf("thread %d returns\n",tid); pthread_exit(NULL);} main(void){ int tid1,tid2; printf(“hello\n”); pthread_create(&tid1,NULL,child1,NULL); pthread_create(&tid2,NULL,child2,NULL); sleep(10); printf(“main thread exit”);} 线程的线程的例子例子1/* thread1.c*/#include #include #include void *thread_function(void *arg) { int i; for ( i=0; i<5; i++) { printf("Thread says hi!\n"); sleep(1); } return NULL;}int main(void) { pthread_t mythread; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) { printf("error creating thread."); exit(1); }if ( pthread_join ( mythread, NULL ) ) { printf("error joining thread."); exit(1); } exit(0);}/*gcc thread1.c -o thread1 -lpthread */ 线程的例子2/* thread2.c*/#include #include int myglobal=0; void *thread_function(void *arg) { int i,j; for ( i=0; i<5; i++) { j=myglobal; j=j+1; printf("."); fflush(stdout); sleep(1); myglobal=j; } return NULL;} int main(void) { pthread_t mythread; int i; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) { printf("error creating thread."); exit(1); }for ( i=0; i<5; i++) { myglobal=myglobal+1; printf("o"); fflush(stdout); sleep(1); } if ( pthread_join ( mythread, NULL ) ) { printf("error joining thread."); exit(1); } printf("\nmyglobal equals %d\n",myglobal); exit(0); } 主线程和新线程都将myglobal加1 5次。

      结果应是10,为什么是5?答案:新线程覆盖了主线程所做的修改 互斥锁 在linux系统中,提供一种基本的线程同步机制—互斥锁,可以用来保护线程代码中共享数据的完整性n操作系统将保证同时只有一个线程能成功操作系统将保证同时只有一个线程能成功完成对一个互斥锁的加锁操作完成对一个互斥锁的加锁操作n如果一个线程已经对某一互斥锁进行了加如果一个线程已经对某一互斥锁进行了加锁,其他线程只有等待该线程完成对这一锁,其他线程只有等待该线程完成对这一互斥锁解锁后,才能完成加锁操作互斥锁解锁后,才能完成加锁操作 互斥锁函数pthread_mutex_lock(pthread_mutex_t *mptr) 返回:成功0,否则返回错误码nmptr:指向互斥锁的指针n该函数接受一个指向互斥锁的指针作为参数并将其锁定如果互斥锁已经被锁定,调用者将进入睡眠状态函数返回时,将唤醒调用者n如果互斥锁是静态分配的,就将它初始化为常值PTHREAD_MUTEX_INITIALIZER pthread_mutex_unlock(pthread_mutex_t *mptr) 用于互斥锁解锁操作。

      返回:成功0,否则返回错误码 改进的程序#include #include #include #include int myglobal;pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER; void *thread_function(void *arg) { int i,j; for ( i=0; i<5; i++) { pthread_mutex_lock(&mymutex); j=myglobal; j=j+1; printf("."); fflush(stdout); sleep(1); myglobal=j; pthread_mutex_unlock(&mymutex); } return NULL;} int main(void) { pthread_t mythread; int i; if ( pthread_create( &mythread, NULL, thread_function, NULL) ) { printf("error creating thread."); abort(); } for ( i=0; i<5; i++) { pthread_mutex_lock(&mymutex); myglobal=myglobal+1; pthread_mutex_unlock(&mymutex); printf("o"); fflush(stdout); sleep(1); } if ( pthread_join ( mythread, NULL ) ) { printf("error joining thread."); abort(); } printf("\nmyglobal equals %d\n",myglobal); exit(0);} 多线程并发服务器模板void *start_routine( void *arg);int main(void) {intlistenfd, connfd;pthread_ttid;typearg;/* Create TCP socket */……/* Bind socket to address */……/* Listen */……while(1) {/* Accept connection */if ((pthread_create(&tid, NULL, start_routine, (void *)&arg)) /* handle exception */……}……} 给新线程传递参数n由于同一个进程内的所有线程共享内存和变量,因此在传递参数时需作特殊处理,下面参考如下几种方法:n传递参数的普通方法n通过指针传递参数n通过分配arg的空间来传递参数n还可以通过加锁等同步设施来实现传递参数; 通过指针传递参数n这种方法首先将要传递的数据转换成通用指针类型,然后传递给新线程,这种方法首先将要传递的数据转换成通用指针类型,然后传递给新线程,新线程再将其还原成原数据类型:新线程再将其还原成原数据类型:void *start_routine(void *arg);int main(void) {int connfd;…pthread_create(&tid, NULL, start_routine, (void *)connfd);…}void *start_routine(void *arg) {int connfd;connfd =(int ) arg;…}n这种方法虽然简单,但却有这种方法虽然简单,但却有很大的局限性很大的局限性很大的局限性很大的局限性。

      如:要求如:要求arg的类型必须能的类型必须能被正确地转换成通用指针类型,而且可传递的参数只有一个被正确地转换成通用指针类型,而且可传递的参数只有一个 传递参数的普通方法n由于线程创建函数只允许传递一个参数,因此当需要传递多个数据时,应首先将这些数据由于线程创建函数只允许传递一个参数,因此当需要传递多个数据时,应首先将这些数据封装在一个结构中封装在一个结构中void *start_routine(void *arg);struct ARG {int connfd;int other;}int main() { struct ARG arg;…While(1){ if((connfd = accept(sockfd,NULL,NULL))== -1){ … } arg.connfd = connfd; pthread_create(&tid, NULL, start_routine, (void *)&arg); … }}void *start_routine(void *arg) {ARG info;info.connfd = ((ARG *)arg) -> connfd;info.other = ((ARG *)arg) -> other;…}//这种方法有问题,对一个客户可以工作,但多个客户则可能出现问题。

      这种方法有问题,对一个客户可以工作,但多个客户则可能出现问题 通过分配arg的空间来传递n主线程首先为每个新线程分配存储主线程首先为每个新线程分配存储arg的空间,再将的空间,再将arg传递给新线程传递给新线程使用,新线程使用完后要释放该空间使用,新线程使用完后要释放该空间void *start_routine(void *arg);int main(void) {struct ARG * arg;int connfd; …loop {… if((connfd = accept(sockfd,NULL,NULL))== -1){ … }arg = new ARG;arg -> connfd = connfd;pthread_create(&tid, NULL, start_routine, (void *)arg);…}} 通过分配arg的空间来传递void *start_routine(void *arg) {struct ARG info;info.connfd = ((ARG *)arg) ->connfd;… /*handle client*/delete arg;} 多线程并发服务器#include ……#define PORT 1234#define BACKLOG2#define MAXDATASIZE 1000void process_cli(int connectfd, sockaddr_in client);void *start_routine(void *arg);struct ARG {int connfd;sockaddr_in client;};int main(void){intlistenfd, connectfd;pthread_ttid;ARG *arg; 多线程并发服务器(多线程并发服务器(cont.)) struct sockaddr_in server, client; int sin_size;/* Create TCP Socket */ …/* Bind server address to listenfd. */ …/* Listen on listenfd */ …sin_size = sizeof(struct sockaddr_in);while(1) {if ((connectfd = accept(listenfd, (struct sockaddr *)&client,&sin_size)) == -1)/* handle error */arg = new ARG;arg -> connfd = connectfd;memcpy((void *)&arg -> client, &client, sizeof(client));if (pthread_create(&tid, NULL, start_routine, (void *)arg)/* handle erroe */}close(listendfd);} 多线程并发服务器(cont.)void process_cli(int connectfd, sockaddr_in client) {…}void *start_routine(void *arg) {ARG *info;info = (ARG *)arg;process_cli(info -> connfd, info -> client);delete arg;pthread_exit(NULL);} 多线程并发服务器(cont.)需要注意的是:与多进程并发服务器不同的是,由于多个线程间共享相同的内存空间和描述字,因此pthread_create后,不能关闭监听套接字和连接套接字,否则程序不能正常工作。

      点击阅读更多内容
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.