并发模型第贰讲-accept+thread

前言

  今天总结下一个并发模型,利用线程来达到并发的效果。从某种方面来说,现在的操作系统给我们提供了两种方式来进行并发任务的执行,一种是进程,另一种是线程。 相对于前者来说,线程要更轻。所谓更轻,指的是线程的创建、销毁以及切换比起进程来要快速些。

这里介绍accept+thread的并发模型方式。

一、accept+thread 模型

1.1 基本介绍

说实话,从形式来看,这种并发模型的方式与前一篇的accept+fork的方式差不多。 这里是:主线程在一个大循环中阻塞等待连接,有新的连接到来只有,新建一个线程执行具体的业务逻辑处理。 所以上一篇的图修改修改拿来这里应该也是阔以的。

在这里插入图片描述

1.2 代码部分

服务器端的主要代码如下:

主要的框架代码:


#include "com.h"
#include "lib.h"

#include <pthread.h>
#include "str_echo.h"

#define LISTENQ 1024

int main(int argc, char**argv){
    int listenFd,connFd;
    pid_t childPid;
    socklen_t cliLen;

    struct sockaddr_in cliAddr,servAddr;
    
    //socket 
    if((listenFd = socket(AF_INET,SOCK_STREAM,0))<0){
        printf("socket error\n");
        exit(-1);
    }
    
    bzero(&servAddr,sizeof(servAddr));
    servAddr.sin_family=AF_INET;
    servAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servAddr.sin_port=htons(SERV_PORT);
   
    //bind
    if(bind(listenFd,(struct sockaddr*)&servAddr,sizeof(servAddr))<0){
        printf("bind error\n");
        exit(-1);
    }
    
    //listen
    if((listen(listenFd,LISTENQ)) <0){
        printf("listen error\n");
        exit(-1);
    }

    pthread_t t_id;
   
    while(1){
        cliLen = sizeof(cliAddr);
        
        if((connFd = accept(listenFd,(struct sockaddr*)&cliAddr,&cliLen))<0){
            
            printf("accept error\n");
            exit(-1);
        }
        
        printf(" in server main: conndfd is %d\n",connFd);

        int ret = pthread_create(&t_id,NULL,(void*) doit,(void*) connFd);     //创建线程 //???传递参数可能不安全

        if(ret != 0){
           printf("Create pthread error!\n");
           exit(0);
        }

       // pthread_join(t_id,NULL);           //阻塞等待线程结束

    }

    return 0;
}

具体的业务逻辑代码

#include"com.h"
#include "lib.h"

#include <pthread.h>

void str_echo(int sockFd){
    
    ssize_t n;
    char buf[MAXLINE];

    while(1){
        while((n=read(sockFd,buf,MAXLINE))>0){
             Writen(sockFd,buf,n);
        }
        
        if(n<0 && errno == EINTR){
               continue;
        } else if(n<0){
           printf("read error\n");
           exit(-1);

        }else if(0 == n){   //finish echo server
            return ;
        }
    }
}

void *doit(void*arg){

    int intArg = (int) arg;

    pthread_detach(pthread_self());		//设置成自动分离的状态
    str_echo(intArg);
    close(intArg);

    return NULL;
}

注意上述代码借用了UNP中的例子,有很多IO函数来自UNP,篇幅所限,这里没有全部贴出。


二、总结

同样,这里先解决几个遇到有关内核的小问题,最后在对这种并发模型做一个小总结。

 

2.1 几个小问题

 
(1)、有关信号处理的问题,即如果进程(或者)线程处在阻塞睡眠之中,信号发生了,这时候怎么办?

如果把线程或者进程看成正常的执行流的话,信号(有的也称为软件中断)是打断这种正常执行流的一种机制。它通常是异步的,也就是说进程预先不知道何时这种情况会发生。

如果进程正处在正常的执行状态,那么信号来了(未被mask),ok,进程会转去执行信号处理,处理完了之后再回来(和中断处理类似)。

如果进程正处于阻塞状态(比如说正阻塞等待accept上),这个时候发生了信号事件,那么这个时候怎么办呢?

你可能对这个不屑一顾,那就和上面的一样啊,先去进行信号处理,处理完了接着阻塞呗。

 
enen…

事是这么个事,但是如果要深究一下的话,还有有些东西的:
进程如果处在阻塞中,那么它是处于阻塞队列中的,一般情况下,阻塞队列里的进程是不会被系统调度到CPU上执行的(除非它等待的情况发生了,比如说阻塞在accept上的进程发现有连接到来就会重新加入就绪队列。 这是基本的操作系统常识,这里就不说了)。 现在信号来了,为了执行信号处理函数,操作系统不得不把原本应该阻塞的进程调度到CPU上进行执行。

现在,问题来了,执行完信号处理函数,进程该怎么办? 是继续回到阻塞队列,还是接着往下执行? 如果要是继续往下执行的话,原本阻塞的事件还没来呢(从某种程度来说,进程是被“非期待”的事件所唤醒的)?

现在很多操作系统内核,都采用了前者,又内核把进程继续放入到阻塞队列,就像什么事都没发生过一样(实际上执行了信号处理)。 当然也有一些内核会继续往下执行,这个时候如果想重新回到阻塞队列,就必须由上层用户进行信号判断,然后在一个循环里手动重新调用阻塞函数(重新加入阻塞队列中)。

【1】中信号处理这一节有比较详细的描述,可以参考。


(2)、有关线程僵尸的讨论。

在建立线程的过程中,有关于一个线程分离的概念。 新建立的线程,一般来说会使用pthread_detach() 函数,自行分离(或者主线程主动调用 pthread_join()也可以),这么做的目的是为了让线程退出的时候能够“完整的dead”,而不会形成一种“半死不活”的“僵尸”状态。,有的说法叫做“僵尸线程”。

上述操作和进程中所做的差不多, 但是我在做实验的时候,没有像进程那样,确切的观察到“僵尸线程”。

在这里插入图片描述

如上文所示,使用命令

top -Hp {服务端进程pid}

然后在客户端终止一个连接,按理说如果在程序中没有detach, 会产生僵尸线程,但是上面没有显示出来。 这个我也没搞太清楚,有小伙伴了解的,希望可以不吝赐教。

2.2 accept+thread 模型小总结

有些说法把这种模型称作thread-per-connection, 相对于上一个accept+fork 模型来说,线程的开销比较小。但是相对来说还是不太适合于短连接(创建线程的开销和短连接开销差不多)。 然而和上篇类似, 这两种方案都受到线程数量(或者进程数量)的影响,如果太多的话,操作系统吃不消的。


参考

【1】、Unix 网络编程(卷1)
【2】、僵尸线程
【3】、 Linux多线程模型
【4】、Linux进程、线程模型,LWP,pthread_self()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值