• 上一篇文章使用fork函数实现了进程并发服务器,但是也提到了一些问题: fork是昂贵的。fork时需要复制父进程的所有资源,包括内存映象、描述字等; 目前的实现使用了一种写时拷贝(copy-on-write)技术,可有效...

      上一篇文章使用fork函数实现了多进程并发服务器,但是也提到了一些问题:

    1. fork是昂贵的。fork时需要复制父进程的所有资源,包括内存映象、描述字等;
    2. 目前的实现使用了一种写时拷贝(copy-on-write)技术,可有效避免昂贵的复制问题,但fork仍然是昂贵的;
    3. fork子进程后,父子进程间、兄弟进程间的通信需要进程间通信IPC机制,给通信带来了困难;
    4. 多进程在一定程度上仍然不能有效地利用系统资源;
    5. 系统中进程个数也有限制。

      下面就介绍实现并发服务器的另外一种方式,使用多线程实现。多线程有助于解决以上问题。

    线程基础

      关于线程的概念就不介绍了,先了解一下linux下线程的一些基本操作。

    线程基础函数

    • pthread_create 创建线程

      pthread_create 函数用于创建新线程。当一个程序开始运行时,系统产生一个称为初始线 程或主线程的单个线程。额外的线程需要由 pthread_create 函数创建。 pthread_create 函数原型如下:

    #include <pthread.h> 
    int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg); 

      如果新线程创建成功,参数 tid 返回新生成的线程 ID。一个进程中的每个线程都由一个 线程 ID 标识,其类型为 pthread_t。attr 指向线程属性的指针。每个线程有很多属性包括:优 先级、起始栈大小、是否是守护线程等等。通常将 attr 参数的值设为 NULL,这时使用系统 默认的属性。
      但创建完一个新的线程后,需要说明它将执行的函数。函数的地址由参数 func 指定。该函数必须是一个静态函数,它只有一个通用指针作为参数,并返回一个通用指针。该执行函 数的调用参数是由 arg 指定,arg 是一个通用指针,用于往 func 函数中传递参数。如果需要传递多个参数时,必须将它们打包成一个结构,然后让 arg 指向该结构。线程以调用该执行 函数开始。
      如果函数调用成功返回 0,出错则返回非 0。

      常见的返回错误值:

    EAGAIN:超过了系统线程数目的限制。
    ENOMEN:没有足够的内存产生新的线程。
    EINVAL:无效的属性attr值。

      示例代码:

    #include <pthread.h>
    #include <stdio.h>
    pthread_t  tid;
    void *ex()
    {
        printf("this is a thread");
    }
    void main()
    {
        pthread_create(&tid,NULL,ex,NULL);
    }

      给线程传递参数:

    void *function(void *arg); 
    struct ARG { 
         int connfd; 
         int other;   //other data 
     }; 
     void main()  
     { 
         struct ARG arg; 
         int connfd,sockfd; 
         pthread_t tid; 
         //...
         While(1) 
         {     
             if((connfd = accept(sockfd,NULL,NULL))== -1) 
             { 
                  //handle exception                    
              } 
              arg.connfd = connfd; 
              if(pthread_create(&tid, NULL, funtion, (void *)&arg)) 
              { 
                  // handle exception 
              } 
          } 
     } 
     void *funtion(void *arg) 
     { 
        struct  ARG info; 
        info.connfd = ((struct ARG *)arg) -> connfd; 
        info.other = ((struct ARG *)arg) -> other; 
        //… 
        close(info.connfd); 
        pthread_exit(NULL); 
     }
    • pthread_join
        看这个函数首先提出一个概念,线程的类型。线程分为两类:可联合的和分离的。

      1. 默认情况下线程都是可联合的。可联合的线程终止 时,其线程 ID 和终止状态将保留,直到线程调用 pthread_join 函数。
      2. 而分离的线程退出后, 系统将释放其所有资源,其他线程不能等待其终止。如果一个线程需要知道另一个线程什么 时候终止,最好保留第二个线程的可联合性。

      pthread_join 函数与进程的 waitpid 函数功能类似,等待一个线程终止。
    pthread_join 函数原型如下:

    #inlcude <pthread.h>
    int pthread_join(pthread_t tid, void **status); 

      参数 tid 指定所等待的线程 ID。该函数必须指定要等待的线程,不能等待任一个线程结束。要求等待的线程必须是当前进程的成员,并且不是分离的线程或守护线程。
      几个线程不 能同时等待一个线程完成,如果其中一个成功调用 pthread_join 函数,则其他线程将返回 ESRCH 错误。
      如果等待的线程已经终止,则该函数立即返回。如果参数 status 指针非空,则 指向终止线程的退出状态值。
      该函数如果调用成功则返回 0,出错时返回正的错误码。

    • pthread_detach
        pthread_detach 函数将指定的线程变成分离的。 pthread_detach 函数原型如下:
    #inlcude <pthread.h> 
    int pthread_detach(pthread_t tid) ;

      参数 tid 指定要设置为分离的线程 ID。

    • pthread_self
        每一个线程都有一个 ID,pthread_self 函数返回自己的线程 ID。 pthread_self 函数原型如下:
    #inlcude <pthread.h> 
    pthread_t pthread_self(void); 

      参数 tid 指定要设置为分离的线程 ID。 函数返回调用函数的线程 ID。
      例如,线程可以通过如下语句,将自己设为可分离的:

    pthread_detach(pthread_self()); 
    • pthread_exit
        函数 pthread_exit 用于终止当前线程,并返回状态值,如果当前线程是可联合的,则其 退出状态将保留。 pthread_exit函数原型如下:
    #include <pthread.h> 
    void pthread_exit(void *status);

      参数 status 指向函数的退出状态。这里的 status 不能指向一个局部变量,因为当前线程 终止后,其所有局部变量将被撤销。
      该函数没有返回值。
      还有两种方法可以使线程终止:

    1. 启动线程的函数 pthread_create 的第三个参数返回。该返回值就是线程的终止状态。
    2. 如果进程的 main 函数返回或者任何线程调用了 exit 函数,进程将终止,线程将随之 终止。

      下面可以看一下多线程并发服务器的实例了,需要注意的是,线程建立后,父、子线程不需要关闭任何的描述符,因为线程中使用的描述符是共享进程中的数据。

    #include <stdio.h>  
    #include <stdlib.h>  
    #include <string.h>  
    #include <unistd.h>  
    #include <sys/types.h>  
    #include <sys/socket.h>  
    #include <netinet/in.h>  
    #include <arpa/inet.h>  
    #include <pthread.h>  
    
    #define PORT 1234  
    #define BACKLOG 5  
    #define MAXDATASIZE 1000  
    
    void process_cli(int connfd, struct sockaddr_in client);  
    void *function(void* arg);  
    struct ARG {  
        int connfd;  
        struct sockaddr_in client;  
    };  
    
    void main()  
    {  
        int listenfd,connfd;  
        pthread_t  tid;  
        struct ARG *arg;  
        struct sockaddr_in server;  
        struct sockaddr_in client;  
        socklen_t  len;  
    
        if ((listenfd =socket(AF_INET, SOCK_STREAM, 0)) == -1) {  
            perror("Creatingsocket 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, sizeof(server)) == -1) {  
            perror("Bind()error.");  
            exit(1);  
        }  
    
        if(listen(listenfd,BACKLOG)== -1){  
            perror("listen()error\n");  
            exit(1);  
        }  
    
        len=sizeof(client);  
        while(1)  
        {  
            if ((connfd =accept(listenfd,(struct sockaddr *)&client,&len))==-1) {  
                perror("accept() error\n");  
                exit(1);  
            }  
            arg = (struct ARG *)malloc(sizeof(struct ARG));  
            arg->connfd =connfd;  
            memcpy((void*)&arg->client, &client, sizeof(client));  
    
            if(pthread_create(&tid, NULL, function, (void*)arg)) {  
                perror("Pthread_create() error");  
                exit(1);  
            }  
        }  
        close(listenfd);  
    }  
    
    void process_cli(int connfd, struct sockaddr_in client)  
    {  
        int num;  
        char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];  
    
        printf("Yougot a connection from %s. \n ",inet_ntoa(client.sin_addr) );  
        num = recv(connfd,cli_name, MAXDATASIZE,0);  
        if (num == 0) {  
            close(connfd);  
            printf("Clientdisconnected.\n");  
            return;  
        }  
        cli_name[num - 1] ='\0';  
        printf("Client'sname is %s.\n",cli_name);  
    
        while (num =recv(connfd, recvbuf, MAXDATASIZE,0)) {  
            recvbuf[num] ='\0';  
            printf("Receivedclient( %s ) message: %s",cli_name, recvbuf);  
            int i;  
            for (i = 0; i <num - 1; i++) {  
                if((recvbuf[i]>='a'&&recvbuf[i]<='z')||(recvbuf[i]>='A'&&recvbuf[i]<='Z'))  
                {  
                    recvbuf[i]=recvbuf[i]+ 3;  
                    if((recvbuf[i]>'Z'&&recvbuf[i]<='Z'+3)||(recvbuf[i]>'z'))  
                    recvbuf[i]=recvbuf[i]- 26;  
                }  
                sendbuf[i] =recvbuf[i];  
            }  
            sendbuf[num -1] = '\0';  
            send(connfd,sendbuf,strlen(sendbuf),0);  
        }  
        close(connfd);  
    }  
    
    void *function(void* arg)  
    {  
        struct ARG *info;  
        info = (struct ARG*)arg;  
        process_cli(info->connfd,info->client);  
        free (arg);  
        pthread_exit(NULL);  
    }  

    线程安全性

      上面的示例代码服务器端的业务逻辑都比较简单,没有涉及到共享数据产生的同步问题。在某些情况下,我们需要多个线程共享全局数据,在访问这些数据时就需要用到同步锁机制。而在共享线程内的全局数据时,可以使用Linux提供的线程特定数据TSD解决。

    同步机制

      在linux系统中,提供一种基本的进程同步机制—互斥锁,可以用来保护线程代码中共享数据的完整性。
      操作系统将保证同时只有一个线程能成功完成对一个互斥锁的加锁操作。
      如果一个线程已经对某一互斥锁进行了加锁,其他线程只有等待该线程完成对这一互斥锁解锁后,才能完成加锁操作。

    互斥锁函数

    pthread_mutex_lock(pthread_mutex_t  *mptr)

    参数说明:

    mptr:指向互斥锁的指针。
    该函数接受一个指向互斥锁的指针作为参数并将其锁定。如果互斥锁已经被锁定,调用者将进入睡眠状态。函数返回时,将唤醒调用者。
    如果互斥锁是静态分配的,就将mptr初始化为常值PTHREAD_MUTEX_INITIALIZER。 

      锁定成功返回0,否则返回错误码。

    pthread_mutex_unlock(pthread_mutex_t  *mptr);

      用于互斥锁解锁操作。成功返回0,否则返回错误码。

    示例代码:

    #include <pthread.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    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);
    }

    运行效果

    线程私有数据

      在多线程环境里,应避免使用静态变量。在 Linux 系统中提供 了线程特定数据(TSD)来取代静态变量。它类似于全局变量,但是,是各个线程私有的, 它以线程为界限。TSD 是定义线程私有数据的惟一方法。同一进程中的所有线程,它们的同 一特定数据项都由一个进程内惟一的关键字 KEY 来标志。用这个关键字,线程可以存取线程私有数据。 在线程特定数据中通常使用四个函数。

    • pthread_key_create
    #include <pthread.h> 
    int pthread_key_create(pthread_key_t *key, void (* destructor)(void *value)); 

      pthread_key_create 函数在进程内部分配一个标志 TSD 的关键字。
      参数 key 指向创建的关 键字,该关键字对于一个进程中的所有线程是惟一的。所以在创建 key 时,每个进程只能调 用一次创建函数 pthread_key_create。在 key 创建之前,所有线程的关键字值是 NULL。一旦 关键字被建立,每个线程可以为该关键字绑定一个值。这个绑定的值对于线程是惟一的,每 个线程独立维护。
       参数 destructor 是一个可选的析构函数,可以和每个关键字联系起来。如果一个关键字 的 destructor 函数不为空,且线程为该关键字绑定了一个非空值,那么在线程退出时,析构函 数将会被调用。对于所有关键字的析构函数,执行顺序是不能指定的。
      该函数正常执行后返回值为 0,否则返回错误码。

    • pthread_once
    #include <pthread.h> 
    int pthread_once(pthread_once_t *once, void (*init) (void)); 

      pthread_once 函数使用 once 参数所指的变量,保证每个进程只调用一次 init 函数。通常 once 参数取常量 PTHREAD_ONCE_INIT,它保证每个进程只调用一次 init 函数。
      该函数正常执行后返回值为 0,否则返回错误码。

    • pthread_setspecific
    #include <pthread.h> 
    int pthread_setspecific(pthread_key_t key, const void *value);

      pthread_setspecific 函数为 TSD 关键字绑定一个与本线程相关的值。
      参数 key 是 TSD 关 键字。
      参数 value 是与本线程相关的值。value 通常指向动态分配的内存区域。
      该函数正常执行后返回值为 0,否则返回错误码。

    • pthread_getspecific
    #include <pthread.h> 
    void * pthread_getspecific(pthread_key_t key); 

      pthread_getspecific 函数获取与调用线程相关的 TSD 关键字所绑定的值。
      参数 key 是 TSD 关键字。
      该函数正常执行后返回与调用线程相关的 TSD 关键字所绑定的值。否则返回 NULL。

      线程安全性代码示例:

    #include <stdio.h>
    #include <pthread.h>
    pthread_key_t   key;
    void echomsg(int t)
    {
        printf("destructor excuted in thread %d,param=%d\n", pthread_self(), t);
    }
    void * child1(void *arg)
    {
        int tid = pthread_self();
        printf("thread1 %d enter\n", tid);
        pthread_setspecific(key, (void *)tid);
        sleep(2);
        printf("thread1 %d key’s %d\n", tid, pthread_getspecific(key));
        sleep(5);
    }
    void * child2(void *arg)
    {
        int tid = pthread_self();
        printf("thread2 %d enter\n", tid);
        pthread_setspecific(key, (void *)tid);
        sleep(1);
        printf("thread2 %d key’s %d\n", tid, pthread_getspecific(key));
        sleep(5);
    }
    int main(void)
    {
        pthread_t tid1, tid2;
    
        printf("hello\n");
        pthread_key_create(&key, (void *)echomsg);
        pthread_create(&tid1, NULL, child1, NULL);
        pthread_create(&tid2, NULL, child2, NULL);
        sleep(10);
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        pthread_key_delete(key);
        printf("main thread exit\n");
        return 0;
    }

    运行结果

    小结

      使用多线程实现并发服务器的优点是线程的开销小,切换容易。但是由于线程共享相同 的内存区域,所以在对共享数据的进行操作时,要注意同步问题。其中线程特定数据虽然实现起来比较烦琐,但是它是将一个非线程安全 函数转换成线程安全函数的常用方法。
      除此之外,还可以通过改变调用函数参变量的方式实现线程的安全性,这里不作介绍。
      下一篇文章将介绍另外一种实现并发服务器的方法:I/O 多路复用。

    展开全文
  • linux 多线程并发服务器(TCP) ​ 所谓多线程并发服务器就是基于线程,每个客户端来了创建一个线程,由线程去处理客户端的请求。相对于多线程服务器来说,多进程服务器在创建进程时要消耗较大的系统资源,所以我们...

    linux 多线程并发服务器(TCP)

    ​ 所谓多线程并发服务器就是基于线程,每个客户端来了创建一个线程,由线程去处理客户端的请求。相对于多线程服务器来说,多进程服务器在创建进程时要消耗较大的系统资源,所以我们使用线程来取代进程,这样服务处理程序可以较快的创建。

    ​ 据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为“轻量级”进程。线程与进程不同的是:一个进程内的所有线程共享相同的全局内存、全局变量等信息,这种机制又带来了同步问题。

    CP多线程并发服务器处理框架:

    #include"头文件"
    
    int main()
    {
        创建套接字sockfd
        绑定套接字(bind)
        监听套接字(listen)
        while1)
        {
            int connfd = accept(....);
            pthread_t thread_tid;
            pthread_create(...);
            pthread_detch(thread_tid);
        }
        close(sockfd);
        return 0;
    }
    //线程处理函数
    void* fun(coid* arg)
    {
    
        int connfd = (int)arg;
        fun();//处理函数
        close(connfd);
    }

    接下来是一个例子:

    utili.h

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>                     
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>                  
    
    #define PORT 8888
    

    ser.c

    #include"utili.h"
    
    
    void* client_fun(void *arg)
    {
        int recvlen = 0;
        char recvbuf[1024] = "";
        int connfd = (int)arg;
        while((recvlen = recv(connfd,recvbuf,sizeof(recvbuf),0))>0)
        {
            printf("recv_buf: %s\n", recvbuf);
            send(connfd, recvbuf, recvlen, 0);
        }
    
        printf("client closed!\n");
        close(connfd);
        return NULL;
    }
    
    int main()
    {
        int sockfd;
        int log;
        int connfd;
        pthread_t thread_id;
        //chushihua 
        struct sockaddr_in server,client;
        bzero(&server,sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(PORT);
        server.sin_addr.s_addr = htonl(INADDR_ANY);
    
        //创建套接字
        sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(sockfd < 0)
            printf("socket create error\n");
        log = bind(sockfd,(struct sockaddr*)&server,sizeof(server));
        if(log < 0)
        {
            printf("bindfd error\n");
            close(sockfd);
            exit(-1);
        }
    
        log = listen(sockfd,20);
        if(log < 0)
        {
            printf("listen error\n");
            close(sockfd);
            exit(-1);
        }
        printf("waiting client >>>>>\n");
    
        while(1)
        {
            socklen_t client_len = sizeof(client);
            //accept
        connfd = accept(sockfd,(struct sockaddr*)&client,&client_len);
        if(connfd < 0)
        {
            printf("connfd error\n");
            continue;
        }
        char cli_ip[INET_ADDRSTRLEN] = "";
        inet_ntop(AF_INET, &client.sin_addr, cli_ip, INET_ADDRSTRLEN);
        printf("----------------------------------------------\n");
        printf("client ip=%s,port=%d\n", cli_ip,ntohs(client.sin_port));
    
        pthread_create(&thread_id,NULL,(void*)client_fun,(void*)connfd);
        pthread_detach(thread_id);//线程分离,结束时自动回收线程
        }
        close(sockfd);
        return 0;
    }

    cli.c

    #include"utili.h"
    
    
    int main()
    {
        int sockfd;
        int log;
        struct sockaddr_in server;
        bzero(&server,sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(PORT);
        server.sin_addr.s_addr = htonl(INADDR_ANY);
    
    
        sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(sockfd < 0)
        {
            printf("sockfd error\n");
            exit(-1);
        }
        log = connect(sockfd,(struct sockaddr*)&server,sizeof(server));
        if(log < 0)
        {
            printf("connect error\n");
            close(sockfd);
            exit(-1);
        }
    
        char sendbuf[1024];
        char recvbuf[1024];
        while(1)
        {
            fgets(sendbuf,sizeof(sendbuf),stdin);
            send(sockfd,sendbuf,strlen(sendbuf),0);
            recv(sockfd,recvbuf,sizeof(recvbuf),0);
            fputs(recvbuf,stdout);
            memset(sendbuf, 0, sizeof(sendbuf));
            memset(recvbuf, 0, sizeof(recvbuf));
        }
        close(sockfd);
        return 0;
    }
    
    展开全文
  • 常见并发服务器方案 1.iterative(循环式/迭代式)服务器  iterative 只能使用短连接(每处理完一个连接,然后就关闭连接,称为短连接),不能使用长连接,如果使用长连接,意味着write需要转到read,那么整个...

    常见并发服务器方案

    1.iterative(循环式/迭代式)服务器

        iterative 只能使用短连接(每处理完一个连接,然后就关闭连接,称为短连接),不能使用长连接,如果使用长连接,意味着write需要转到read,那么整个程序就是一个单线程程序,如果此时有其它线程过来,没有办法接受连接,因为前一个线程还在read->write的循环中。也就是说如果使用长连接的话,这个程序只能够处理一个客户端,而不能处理多个客户端。要想让程序处理多个客户端,只能使用短连接。这种服务器不是真正意义上的并发服务器,它是循环的,称为循环服务器。 

    这种服务器有很多缺陷 : 

    单线程,不能充分利用多核cpu

    因为是短链接,有可能上一次断开的连接就是本次的连接,这样效率明显下降

    不能并发处理

    2.concurrent(并发式)服务器

    这种服务器,因为是多线程或者多进程的,所以能并发处理请求。
    注:如果是多进程,记得关闭监听套接字,因为子进程会继承套接字。

    如果是多线程,就不用关闭监听套接字了,因为线程没有继承打开的套接字。

    3.prefork or threaded服务器(预先创建进程和线程)

    创建套接字、绑定、监听,预先创建若干个子进程,子进程负责与客户端的通信。原理和并发式服务器类似。不同是预先创建进程或线程,减少了开销。能够提高响应速度。这里有一个问题,当一个客户端连接过来的时候,会有惊群的可能性。

    这种服务器由于多个进程在accept等待中,当一个请求到达时,都会被触发,但是只有一个成功返回。这是一种“惊群”现象。

    如今网络编程中经常用到多进程或多线程模型,大概的思路是父进程创建socket,bind、listen后,通过fork创建多个子进程,每个子进程继承了父进程的socket,调用accpet开始监听等待网络连接。这个时候有多个进程同时等待网络的连接事件,当这个事件发生时,这些进程被同时唤醒,就是“惊群”。这样会导致什么问题呢?我们知道进程被唤醒,需要进行内核重新调度,这样每个进程同时去响应这一个事件,而最终只有一个进程能处理事件成功,其他的进程在处理该事件失败后重新休眠或其他。

    Nginx中使用mutex互斥锁解决这个问题,具体措施有使用全局互斥锁,每个子进程在epoll_wait()之前先去申请锁,申请到则继续处理,获取不到则等待,并设置了一个负载均衡的算法(当某一个子进程的任务量达到总设置量的7/8时,则不会再尝试去申请锁)来均衡各个进程的任务量。

    4.reactive(反应式)服务器(使用的是reactor模式)

    并发处理多个请求,是在一个线程中完成。实际上单线程轮询多个客户端。利用I/O复用(select、poll、epoll)实现。无法充分利用多核CPU,网络编程中的select

    不适合执行时间比较长的服务,所以为了让客户感觉是在“并发”处理而不是“循环”处理,每个请求必须在相对较短时间内执行

    5.reactor+thread per request

    每个请求过来,创建一个线程。这样就能利用多CPU

    6.reactor+worker thread

    7.reactor+thread pool(第5种方案的改进)

    一个客户端来连接,并且发送请求包过来,在Reactor中(这是一个线程)读取请求包,并这个请求包丢到线程池中处理,线程池会取出工作线程对其进行处理。这是即使计算量较大,时间较长,也没有关系,因为是线程池中的线程进行处理的,不会影响到Reactor这个I/O线程,还可以接受其它客户端的连接,所以能够计算密集型任务。

    处理完成之后,线程池中的线程并不负责数据的发送,要响应数据包,还必须丢到I/O线程中来发送或者异步地调用I/O线程的发送方法来发送

    8.multiple reactors(能适应更大的突发I/O)

    多个事件循环,每个reactor都是一个线程或者一个进程。
    reactors in threads(one loop per thread)
    每个线程都有一个reactor,也就是事件循环
    reactors in process
    每个进程都有一个reactor

    每一个reactor都是一个线程(或进程),将监听套接字加入mainReactor,每当客户端连接过来时,监听套接字产生可读事件,acceptor就返回已连接套接字,把返回的已连接套接字加入subReactor里,用它来处理客户端的连接,如果再来一个客户端,就按顺序的分配到下一个subReactor,当第三个客户端连接的时候又回到了第一个subReactor。这种方式称为round robin(轮叫),它能够保证subReactors中处理的事件是均匀的,而不至于每一个事件循环处理的连接过多。每一个连接只能在一个subReactor中处理,而不能在一个subReactor中read,在另一个subReactor中send
    这种方式能够适应突发的I/O请求
     

    9.multiple reactors+thread pool(one loop per thread+threadpool)(突发I/O与密集计算)

    这里的multiple reactors只能用线程来实现,不能用进程,否则后面的线程池无法共享
    多个subReactor共享一个线程池,其实就相当于两个线程池(I/O线程池+计算线程池Threadpoll)
     

    10.proactor服务器(proactor模式,基于异步I/O)

    前面介绍的服务器都是基于同步I/O,异步I/O使得I/O操作和其他操作能够重叠,I/O操作的时候,计算操作也在同时执行
    理论上proactor比reactor效率要高一些
    异步I/O能够让I/O操作与计算重叠。充分利用DMA特性
    Linux异步IO
    glibc aio(aio_*),有bug
    kernel native aio(io_*),也不完美。目前仅支持 O_DIRECT 方式来对磁盘读写,跳过系统缓存。要自已实现缓存,难度不小。

    boost asio实现的proactor,实际上不是真正意义上的异步I/O,底层是用epoll来实现的,模拟异步I/O的。

    常见并发服务器方案比较

     

    二、一些常见问题

    1、Linux能同时启动多少个线程?

    对于 32-bit Linux,一个进程的地址空间是 4G,其中用户态能访问 3G 左右,而一个线程的默认栈 (stack) 大小是 8M,心算可知,一个进程大约最多能同时启动 350 个线程左右。

    2、多线程能提高并发度吗?

    如果指的是“并发连接数”,不能。
     

    假如单纯采用 thread per connection 的模型,那么并发连接数大约350,这远远低于基于事件的单线程程序所能轻松达到的并发连接数(几千上万,甚至几万)。所谓“基于事件”,指的是用 IO multiplexing event loop 的编程模型,又称 Reactor 模式。

    3、多线程能提高吞吐量吗?

    对于计算密集型服务,不能。

    如果要在一个8核的机器上压缩100个1G的文本文件,每个core的处理能力为200MB/s,那么“每次起8个进程,一个进程压缩一个文件”与“只启动一个进程(8个线程并发压缩一个文件)”,这两种方式总耗时相当,但是第二种方式能较快的拿到第一个压缩完的文件。
     

    4、多线程能提高响应时间吗?

    可以。参考问题3
     

    5、多线程程序日志库要求

    线程安全,即多个线程可以并发写日志,两个线程的日志消息不会出现交织。
    用一个全局的mutex保护IO
    每个线程单独写一个日志文件
    前者造成全部线程抢占一个锁(串行写入)
    后者有可能让业务线程阻塞在写磁盘操作上。(磁盘IO时间比较长)

    解决办法:用一个logging线程负责收集日志消息,并写入日志文件,其他业务线程只管往这个“日志线程”发送日志消息(如通过BlockingQueue提供接口),这称为“异步日志”,也是一个经典的生产者消费者模型。
     

    6、线程池大小的选择

    如果池中执行任务时,密集计算所占时间比重为P(0<P<=1),而系统一共有C个CPU,为了让C个CPU跑满而不过载,线程池大小的经验公式T=C/P,即T*P=C(让CPU刚好跑满 )
    假设C=8,P=1.0,线程池的任务完全密集计算,只要8个活动线程就能让CPU饱和
    假设C=8,P=0.5,线程池的任务有一半是计算,一半是IO,那么T=16,也就是16个“50%繁忙的线程”能让8个CPU忙个不停。
     

    7、线程分类

    I/O线程(这里特指网络I/O)
    计算线程
    第三方库所用线程,如logging,又比如database

    展开全文
  • tcp多线程并发服务器 多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。据统计,创建线程与创建进程要快 10100 ...

    tcp多线程并发服务器

    多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建。据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为“轻量级”进程。线程与进程不同的是:一个进程内的所有线程共享相同的全局内存、全局变量等信息,这种机制又带来了同步问题。

    tcp多线程并发服务器框架:



    我们在使用多线程并发服务器时,直接使用以上框架,我们仅仅修改client_fun()里面的内容。
    代码示例:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>						
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>					
    #include <pthread.h>
    
    /************************************************************************
    函数名称:	void *client_fun(void *arg)
    函数功能:	线程函数,处理客户信息
    函数参数:	已连接套接字
    函数返回:	无
    ************************************************************************/
    void *client_fun(void *arg)
    {
    	int recv_len = 0;
    	char recv_buf[1024] = "";	// 接收缓冲区
    	int connfd = (int)arg; // 传过来的已连接套接字
    
    	// 接收数据
    	while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
    	{
    		printf("recv_buf: %s\n", recv_buf); // 打印数据
    		send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
    	}
    	
    	printf("client closed!\n");
    	close(connfd);	//关闭已连接套接字
    	
    	return 	NULL;
    }
    
    //===============================================================
    // 语法格式:	void main(void)
    // 实现功能:	主函数,建立一个TCP并发服务器
    // 入口参数:	无
    // 出口参数:	无
    //===============================================================
    int main(int argc, char *argv[])
    {
    	int sockfd = 0;				// 套接字
    	int connfd = 0;
    	int err_log = 0;
    	struct sockaddr_in my_addr;	// 服务器地址结构体
    	unsigned short port = 8080; // 监听端口
    	pthread_t thread_id;
    	
    	printf("TCP Server Started at port %d!\n", port);
    	
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);   // 创建TCP套接字
    	if(sockfd < 0)
    	{
    		perror("socket error");
    		exit(-1);
    	}
    	
    	bzero(&my_addr, sizeof(my_addr));	   // 初始化服务器地址
    	my_addr.sin_family = AF_INET;
    	my_addr.sin_port   = htons(port);
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	
    	printf("Binding server to port %d\n", port);
    	
    	// 绑定
    	err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	// 监听,套接字变被动
    	err_log = listen(sockfd, 10);
    	if( err_log != 0)
    	{
    		perror("listen");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	printf("Waiting client...\n");
    	
    	while(1)
    	{
    		char cli_ip[INET_ADDRSTRLEN] = "";	   // 用于保存客户端IP地址
    		struct sockaddr_in client_addr;		   // 用于保存客户端地址
    		socklen_t cliaddr_len = sizeof(client_addr);   // 必须初始化!!!
    		
    		//获得一个已经建立的连接	
    		connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);   							
    		if(connfd < 0)
    		{
    			perror("accept this time");
    			continue;
    		}
    		
    		// 打印客户端的 ip 和端口
    		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    		printf("----------------------------------------------\n");
    		printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
    		
    		if(connfd > 0)
    		{
    			//由于同一个进程内的所有线程共享内存和变量,因此在传递参数时需作特殊处理,值传递。
    			pthread_create(&thread_id, NULL, (void *)client_fun, (void *)connfd);  //创建线程
    			pthread_detach(thread_id); // 线程分离,结束时自动回收资源
    		}
    	}
    	
    	close(sockfd);
    	
    	return 0;
    }

    运行结果:


    注意
    1.上面pthread_create()函数的最后一个参数是void *类型,为啥可以传值connfd
    while(1)
    {
    	int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
    	pthread_create(&thread_id, NULL, (void *)client_fun, (void *)connfd); 
    	pthread_detach(thread_id);  
    }
    

    因为void *是4个字节,而connfd为int类型也是4个字节,故可以传值。如果connfd为char、short,上面传值就会出错


    2.上面pthread_create()函数的最后一个参数是可以传地址吗?可以,但会对服务器造成不可预知的问题

    while(1)
    {
    	int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
    	pthread_create(&thread_id, NULL, (void *)client_fun, (void *)&connfd); 
    	pthread_detach(thread_id);  
    }
    

    原因:假如有多个客户端要连接这个服务器,正常的情况下,一个客户端连接对应一个 connfd,相互之间独立不受影响,但是,假如多个客户端同时连接这个服务器,A 客户端的连接套接字为 connfd,服务器正在用这个 connfd 处理数据,还没有处理完,突然来了一个 B 客户端,accept()之后又生成一个 connfd, 因为是地址传递, A 客户端的连接套接字也变成 B 这个了,这样的话,服务器肯定不能再为 A 客户端服务器了


    2.如果我们想将多个参数传给线程函数,我们首先考虑到就是结构体参数,而这时传值是行不通的,只能传递地址

    这时候,我们就需要考虑多任务的互斥或同步问题了,这里通过互斥锁来解决这个问题,确保这个结构体参数值被一个临时变量保存过后,才允许修改。

    #include <pthread.h>  
      
    pthread_mutex_t mutex;  // 定义互斥锁,全局变量  
      
    pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的  
      
    // 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞  
    pthread_mutex_lock(&mutex);   
    int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);  
      
    //给回调函数传的参数,&connfd,地址传递  
    pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd);  //创建线程  
      
    // 线程回调函数  
    void *client_process(void *arg)  
    {  
        int connfd = *(int *)arg; // 传过来的已连接套接字  
          
        // 解锁,pthread_mutex_lock()唤醒,不阻塞  
        pthread_mutex_unlock(&mutex);   
          
        return  NULL;  
    }  

    示例代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>						
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>					
    #include <pthread.h>
    
    pthread_mutex_t mutex;	// 定义互斥锁,全局变量
    
    /************************************************************************
    函数名称:	void *client_process(void *arg)
    函数功能:	线程函数,处理客户信息
    函数参数:	已连接套接字
    函数返回:	无
    ************************************************************************/
    void *client_process(void *arg)
    {
    	int recv_len = 0;
    	char recv_buf[1024] = "";	// 接收缓冲区
    	int connfd = *(int *)arg; // 传过来的已连接套接字
    	
    	// 解锁,pthread_mutex_lock()唤醒,不阻塞
    	pthread_mutex_unlock(&mutex); 
    	
    	// 接收数据
    	while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
    	{
    		printf("recv_buf: %s\n", recv_buf); // 打印数据
    		send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
    	}
    	
    	printf("client closed!\n");
    	close(connfd);	//关闭已连接套接字
    	
    	return 	NULL;
    }
    
    //===============================================================
    // 语法格式:	void main(void)
    // 实现功能:	主函数,建立一个TCP并发服务器
    // 入口参数:	无
    // 出口参数:	无
    //===============================================================
    int main(int argc, char *argv[])
    {
    	int sockfd = 0;				// 套接字
    	int connfd = 0;
    	int err_log = 0;
    	struct sockaddr_in my_addr;	// 服务器地址结构体
    	unsigned short port = 8080; // 监听端口
    	pthread_t thread_id;
    	
    	pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的
    	
    	printf("TCP Server Started at port %d!\n", port);
    	
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);   // 创建TCP套接字
    	if(sockfd < 0)
    	{
    		perror("socket error");
    		exit(-1);
    	}
    	
    	bzero(&my_addr, sizeof(my_addr));	   // 初始化服务器地址
    	my_addr.sin_family = AF_INET;
    	my_addr.sin_port   = htons(port);
    	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    	
    	
    	printf("Binding server to port %d\n", port);
    	
    	// 绑定
    	err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    	if(err_log != 0)
    	{
    		perror("bind");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	// 监听,套接字变被动
    	err_log = listen(sockfd, 10);
    	if( err_log != 0)
    	{
    		perror("listen");
    		close(sockfd);		
    		exit(-1);
    	}
    	
    	printf("Waiting client...\n");
    	
    	while(1)
    	{
    		char cli_ip[INET_ADDRSTRLEN] = "";	   // 用于保存客户端IP地址
    		struct sockaddr_in client_addr;		   // 用于保存客户端地址
    		socklen_t cliaddr_len = sizeof(client_addr);   // 必须初始化!!!
    		
    		// 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞
    		pthread_mutex_lock(&mutex);	
    		
    		//获得一个已经建立的连接	
    		connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);   							
    		if(connfd < 0)
    		{
    			perror("accept this time");
    			continue;
    		}
    		
    		// 打印客户端的 ip 和端口
    		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
    		printf("----------------------------------------------\n");
    		printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
    		
    		if(connfd > 0)
    		{
    			//给回调函数传的参数,&connfd,地址传递
    			pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd);  //创建线程
    			pthread_detach(thread_id); // 线程分离,结束时自动回收资源
    		}
    	}
    	
    	close(sockfd);
    	
    	return 0;
    }


    运行结果:


    注意:这种用互斥锁对服务器的运行效率有致命的影响





    展开全文
  • //server.c #include"util.h" #define SER_IP "127.0.0.1" #define SER_PORT 6666 struct infor { struct sockaddr_in cliaddr; int connfd; }; void* thread_fun(void *arg) ... char buf[BUFSIZ],str[INET_ADDR...
    //server.c
    
    #include"util.h"
    #define SER_IP "127.0.0.1"
    #define SER_PORT 6666
    struct infor
    {
    	struct sockaddr_in cliaddr;
    	int connfd;
    };
    void* thread_fun(void *arg)
    {
    	char buf[BUFSIZ],str[INET_ADDRSTRLEN];//16
    	struct infor *var=(struct infor*)arg;
    	int i,n;
    
    	printf("Received from %s at port %d\n",
    			inet_ntop(AF_INET,&(var->cliaddr.sin_addr.s_addr),str,sizeof(str)),
    			ntohs(var->cliaddr.sin_port));
    	while(1)
    	{
    		n=Read(var->connfd,buf,sizeof(buf));
    		if(n==0)
    		{
    			printf("The Client %d closed!\n",var->connfd );
    			break;
    		}
    		else 
    		{
    			for(i=0;i<n;i++)
    				buf[i]=toupper(buf[i]);
    			write(STDOUT_FILENO,buf,n);//写至屏幕
    			write(var->connfd,buf,n);//回写给客户端
    		}
    	}
    	close(var->connfd);
    
    }
    int main(int argc,char *argv[])
    {
    	int listenfd,connfd;
    	int n,i=0;
    	pthread_t tid;
    	struct infor ts[128];
    
    	struct sockaddr_in serv_addr,cliaddr;
    	listenfd=start_up(SER_IP,SER_PORT);
    
    	printf("Accepting client connections.................\n ");
    	while(1)
    	{
    		socklen_t addrlen=sizeof(struct sockaddr);
    		connfd=Accept(listenfd,(struct sockaddr*)&cliaddr,&addrlen);
    		ts[i].cliaddr=cliaddr;
    		ts[i].connfd=connfd;
    		pthread_create(&tid,NULL,thread_fun,(void *)&ts[i]);
    
    		pthread_detach(tid);//子线程分离,防止僵线程产生
    		i++;
    	}
    	return 0;
    }
    /////////////////////////////////////////////////
    
    util.h
    
    #include<stdio.h>
    #include<sys/socket.h>
    #include<ctype.h>
    #include<string.h>
    #include<stdlib.h>
    #include<arpa/inet.h>
    #include<ctype.h>
    #include<unistd.h>
    #include<errno.h>
    #include<pthread.h>
    #include<pthread.h>
    #define SERPORT 6666
    #define backlogs 10
    void perr_exit(const char *s)
    {
    	perror(s);
    	exit(1);
    }
    int Socket(int domain,int type,int protocol)
    {
    	int fd=socket(domain,type,protocol);
    	if(fd==-1)
    		perr_exit("socket error!\n");
    	return fd;
    }
    int Bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
    {
    	int ret=bind(sockfd,addr,addrlen);
    	if(ret==-1)	
    		perr_exit("bind error!\n");
    	return ret;
    }
    int Listen(int sockfd,int backlog)
    {
    	int ret=listen(sockfd,backlog);
    	if(ret==-1)	
    		perr_exit("listen error!\n");
    	return ret;
    }
    int Accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)//慢速系统调用
    {
    	int ret;
    again:
    	if( (ret=accept(sockfd,addr,addrlen)) ==-1)
    	{
    		if(errno==ECONNABORTED || errno==EINTR)
    			goto again;
    		else
    			perr_exit("accept error!\n");
    	}
    	return ret;
    }
    int start_up(const char* ip,int port)
    {
    	int fd=Socket(AF_INET,SOCK_STREAM,0);
    	struct sockaddr_in ser_addr;
    	ser_addr.sin_family=AF_INET;
    	ser_addr.sin_port=htons(port);
    	ser_addr.sin_addr.s_addr=inet_addr(ip);
    	int on=1;
    	setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int));
    	socklen_t addrlen=sizeof(struct sockaddr);
    	Bind(fd,(struct sockaddr*)&ser_addr,addrlen);
    	Listen(fd,backlogs);
    	return fd;
    }
    //connect() 不会阻塞
    int Connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
    {
    	int ret;
    	ret=connect(sockfd,addr,addrlen);
    	if(ret==-1)
    		perr_exit("connect error!\n");
    	return ret;
    }
    
    ssize_t Read(int fd,void *buf,size_t nbytes)
    {
    	ssize_t n;
    again:
    	if( (n=read(fd,buf,nbytes))==-1)
    	{
    		if(errno==EINTR)
    			goto again;
    		else
    			perr_exit("read errror!\n");
    	}
    	return n;
    }
    
    ssize_t Write(int fd,void *buf,size_t nbytes)
    {
    	ssize_t n;
    again:
    	if( (n=write(fd,buf,nbytes))==-1)
    	{
    		if(errno==EINTR)
    			goto again;
    		else
    			perr_exit("write errror!\n");
    	}
    	return n;
    }
    
    
    
    

     

    展开全文
  • #include &lt;sys/types.h&gt; #include &lt;sys/socket.h&gt; #include &lt;netinet/in.h&gt; #include &lt;arpa/inet.h&gt; #include &lt;stdio.h&...#inc.
  • 并发服务器编程之多进程并发服务器 一、多线程服务器分析:多进程并发与多线程并发实现过程差不多,只是多线程的同步、资源回收与多进程还是有很多区别的。多进程不需要记录子进程的信息,而多线程需要记录。 ...
  • 并发服务器设计技术一般有:多进程服务器、多线程服务器、I/O复用服务器等。二、多进程并发服务器Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器。多进程服务器是当客...
  • 服务器设计技术有很,按使用的协议来分有 TCP 服务器和 UDP 服务器,按处理方式来分有循环服务器和并发服务器。循环服务器与并发服务器模型在网络程序里面,一般来说都是许多客户对应一个服务器(对一),为了...
  • 常见多线程并发服务器设计方案举例 一、3点基础知识 1、一个主机的端口号为所有进程所共享,但普通用户进程绑定bind不了一些特殊端口号如20、80等。   多个进程不能同时监听listen同一个端口,会...
  • 然而在实际应用中,不可能让一个服务器长时间地为一个客户服务,而需要其具有同时处理 个客户请求的能力,这种同时可以处理个客户请求的服务器称为并发服务器,其效率很 高却实现复杂。在实际应用中,并发服
  • linux UDP并发服务器

    2018-09-06 06:41:06
    主要是采用队列、多线程的方法。后面会给出一个简单的实现例子,以供大家参考。功能方面较为简单,以后会慢慢完善。 现将思路整理如下,有兴趣的同学可以一起讨论。代码稍后公布。 众所周知,通常所见的的TCP...
  • 如果在文章中发现代码错误或其它问题请告知,感谢! 2.代码实现 man.c代码如下: #include&lt;stdio.h&gt; #include &lt;string.h&gt; #include &lt;sys/select.h&gt; #include &...
  • 多线程并发服务器

    2017-03-25 16:08:31
    进程并发服务器的应用程序中,父进程accept一个连接,fork一个子进程,该子进程负责处理与该连接对端的客户之间的通信。 尽管进程的编程模型中,各进程拥有独立的地址空间,减少了出错的概率,然而,fork调用...
  • 话不多说,实现了多个客户端可以交换信息的简单聊天程序,程序如下:chat_server.c//多线程并发服务器端 //访问全局变量clnt_cnt和clnt_socks的代码将构成临界区 #include &lt;stdio.h&gt; #include &lt...
  • 在做网络服务的时候tcp并发服务端程序的编写必不可少。tcp并发通常有几种固定的设计模式套路,他们各有优点,也各有应用之处。下面就简单的讨论下这几种模式的差异: 单进程,单线程 在accept之后,就开始在这一...
  • 一个在Linux下模拟多线程并发的方法,使用这个方法可以同时批量在定义数量的服务器上执行相关命令,比起普通for/while循环只能顺序一条一条执行的效率高非常多。 1、不使用多线程的情况 /Users/nisj/...
  • linux多线程服务器

    2018-05-29 15:58:09
    上一篇文章使用fork函数实现了进程并发服务器,但是也提到了一些问题:fork是昂贵的。fork时需要复制父进程的所有资源,包括内存映象、描述字等;目前的实现使用了一种写时拷贝(copy-on-write)技术,可有效避免...
1 2 3 4 5 ... 20
收藏数 86,668
精华内容 34,667