精华内容
下载资源
问答
  • 多线程并发服务器

    千次阅读 2017-03-25 15:45:23
    进程并发服务器的应用程序中,父进程accept一个连接,fork一个子进程,该子进程负责处理与该连接对端的客户之间的通信。 尽管进程的编程模型中,各进程拥有独立的地址空间,减少了出错的概率,然而,fork调用...

    在多进程并发服务器的应用程序中,父进程accept一个连接,fork一个子进程,该子进程负责处理与该连接对端的客户之间的通信。
    尽管多进程的编程模型中,各进程拥有独立的地址空间,减少了出错的概率,然而,fork调用却存在一些问题:
    - fork是昂贵的,fork要把父进程的内存映像复制到子进程,并在子进程中复制所有描述符,这个操作是较重量级的。
    - fork返回之后父子进程之间信息的传递需要进程间通信(IPC)机制。

    线程则可以解决上述两个问题。线程有时也称为轻量级的进程,线程的创建可能比进程的创建快10-100倍。同一个进程内所有线程共享相同的全局内存,这使得线程之间易于共享信息,但伴随这种简易性而来的是线程安全问题。

    线程函数

    1. pthread_create 函数

    我们介绍的第一个线程的函数是pthread_create,它的作用是创建一个新线程。它的定义如下:

    #include <pthread.h>
    int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

    这个函数的定义看起来很复杂,其实用起来很简单。
    第一个参数是指向pthread_t类型的指针。线程被创建时,这个指针指向的变量将被写入一个标识符(线程ID)我们用该标识符来引用新线程。
    第二个参数用于设置线程的属性,一般不需要特殊的属性,所以只需要设置该参数为NULL。
    最后两个参数,分别告诉新线程将要启动执行的函数和传递给该函数的参数。
    pthread_create函数在成功调用时返回0,如果失败则返回失败码。

    2. pthread_exit 函数

    线程通过调用pthread_exit函数终止执行。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。

    #include <pthread.h>
    void pthread_exit(void *retval);

    3. pthread_join 函数

    pthread_join函数的作用是等待某个线程的结束。其第一个参数指定了需要等待的线程ID,第二个参数是一个二级指针,它指向另一个指针,而后者指向线程的返回值。

    #include <pthread.h>
    int pthread_join(pthread_t th, void **thread_return);

    4. pthread_self 函数

    每个线程都有一个在所属进程内标识自己的ID。线程ID由phtread_create返回,而且我们已经看到pthread_join也使用了线程ID来指定等待哪个线程。pthread_self的作用是返回自身的线程ID。

    #include <pthread.h>
    pthread_t pthread_self(void);

    5. pthread_detach 函数

    一个线程或者是可汇合(joinable),或者是脱离的(detached)。当一个可汇合的线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join的返回值中。脱离的线程终止时,所有相关资源都被释放,我们不能等待它们终止。如果一个线程需要知道另一个线程什么时候终止,那就最好保持第二个线程的可汇合状态。
    pthread_detach函数把指定的线程转变为脱离的状态。

    #include <pthread.h>
    int pthread_detach(pthread_t th);

    第一个线程例子

    我们的第一个线程的例子如下:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    
    void *thread_function(void *arg);
    
    char message[] = "Hello pthread!";
    
    int main() {
        int res;
        pthread_t a_thread;
        void *thread_result;
    
        res = pthread_create(&a_thread, NULL, thread_function, (void*)message);
        if (res != 0) {
            perror("Thread creation failed.");
            exit(EXIT_FAILURE);
        }
        printf("Waiting for thread to finish..\n");
        res = pthread_join(a_thread, &thread_result);
        if (res != 0) {
            perror("Thread join failed.");
            exit(EXIT_FAILURE);
        }
        printf("Thread joined, it returned %s\n", (char*)thread_result);
        printf("Message is now %s\n", message);
        exit(EXIT_SUCCESS);
    }
    
    void *thread_function(void *arg) {
        printf("Thread_function is running. Argument was %s\n", (char*)arg);
        sleep(3);
        strcpy(message, "Bye!");
        pthread_exit("Thank you for the CPU time.");
    }

    首先,我们调用pthread_create创建了一个新线程,在调用pthread_create函数时,我们向其传递了一个函数指针thread_function,即新线程的执行函数,以及传递给该执行函数的参数message
    创建新线程后,主线程通过pthread_join等待新线程的执行完毕。而新线程执行thread_function函数,修改全局数据message,然后退出线程并向主线程返回一个字符串。主线程等待新线程执行完毕后,获得新线程的返回值和修改后的全局数组。

    编译运行上面的程序,得到以下输出:

    Waiting for thread to finish..
    thread_function is running. Argument was Hello pthread!
    Thread joined, it returned Thank you for the CPU time.
    Message is now Bye!

    基于线程的并发服务器

    下面展示了基于线程的并发服务器的代码。整体结构类似于基于进程的设计。主线程不断地等待连接请求,然后创建一个新线程处理该请求。

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    void *thread_function(void *arg);
    
    int main() {
        int listenfd, *connfdp;
        socklen_t server_len, client_len;
        struct sockaddr_in server_address;
        struct sockaddr_in client_address;
        pthread_t th;
    
        // 创建套接字
        listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
        // 命名套接字
        server_address.sin_family = AF_INET;
        server_address.sin_addr.s_addr = htonl(INADDR_ANY);
        server_address.sin_port = htons(6240);
        server_len = sizeof(server_address);
        bind(listenfd, (struct sockaddr*)&server_address, server_len);
    
        // 创建套接字队列
        listen(listenfd, 5);
    
        // 接受客户的连接
        while (1) {
            printf("server waiting\n");
            connfdp = malloc(sizeof(int));
            client_len = sizeof(client_address);
            *connfdp = accept(listenfd, (struct sockaddr*)&client_address, &client_len);
            // 创建新线程
            pthread_create(&th, NULL, thread_function, connfdp);
        }
    }
    
    void *thread_function(void *arg) {
        int connfd = *((int*)arg);
        printf("Thread_function is running. Argument was %d\n", connfd);
        pthread_detach(pthread_self());
        free(arg);
    
        // 处理客户的请求
        char ch;
        read(connfd, &ch, 1);
        ch++;
        write(connfd, &ch, 1);
    
        close(connfd);
        return NULL;
    }

    代码虽然较简单,但有几个地方值得我们重点关注一下。
    第一个问题是我们在调用pthread_create时,如何将已连接套接字描述符传递给新线程。最容易想到的方法如下:

    connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_len);
    pthread_create(&th, NULL, thread_function, &connfd);

    然后,在线程函数中引用这个指针变量,并将其赋值给一个局部变量。

    void *thread_function(void *arg) {
        int connfd = *((int*)arg);
        ...
    }

    然而,这个做法可能会带来线程安全的问题。如果赋值语句在下一个accept之前完成,那么线程函数中的局部变量connfd将得到正确值。如果赋值语句在下一个accept之后才完成,那么线程函数中的局部变量connfd就会得到下一次连接的描述符的值。这显然不是我们想要的结果。
    为了避免这种情况的出现,每次调用accept返回时,将返回的已连接套接字描述符存储在动态分配的内存中,这样无论线程函数中的赋值先于还是后于下一个accept完成,都不会出现线程安全的问题。

    另一个问题是在线程函数中避免存储器资源泄漏。既然我们不显示式回收线程,我们就必须分离每个线程,使得它们在终止时存储器资源能够被回收。另外,还有一点需要提醒的,在线程函数中必须将主线程分配的动态内存释放了。

    最后一个问题是如何关闭套接字描述符的问题。在基于进程的服务器中,我们在父进程和子进程两个位置都关闭了已连接套接字描述符。但在基于线程的服务器中,我们只需要在线程函数中关闭已连接套接字描述符,而不需要在主线程中关闭。
    在Linux系统中,每个文件或者套接字都有一个引用计数,引用计数在文件表项中维护,它是当前打开着的引用该文件或者套接字的描述符的个数。对于多进程服务器的情形,已连接套接字描述符在父进程和子进程间共享(也就是被复制),因此已连接套接字相关联的文件表项的访问计数值为2,故在父进程和子进程都需要执行close操作。而对于多线程服务器的情形,由于线程间具有相同的地址空间,套接字描述符并不进程复制操作,即已连接套接字描述符的计数值为1,故只需要在创建的新线程中执行一次close操作即可。

    参考资料

    1. 深入理解计算机系统,第2版,机械工业出版社
    2. Linux程序设计(第4版),Neil Matthew等著,人民邮电出版社,2010年
    3. UNIX 网络编程卷1:套接字联网API(第三版), W.Richard Stevens 等著
    4. http://www.tuicool.com/articles/fiEfaa
    展开全文
  • 上一篇文章使用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 多路复用。

    展开全文
  • 关于MFC和SDK的区别,调用多线程并发有什么不同,MFC线程的同步的封装是什么?
  • C++多线程并发(一)--- 线程创建与管理

    千次阅读 多人点赞 2020-03-16 22:21:32
    简单来说,并发指的是两个或个独立的活动在同一时段内交替发生。与并发相近的另一个概念是并行,并行则强调的是个独立的活动在同一时刻点同时发生。 二、为什么使用并发 在应用程序中使用并发的原因主要有两个:...

    前言

    我们都听说过摩尔定律:预计18个月会将芯片的性能提高一倍。早期的计算机时钟频率较低,比如1985年intel 80386 工作频率只有20 MHZ,提升CPU 核心的时钟频率带来的性能收益更大,到2006年Intel Core 2 处理器已经能够达到3.5 GHZ 的工作频率了。从2007年开始,CPU 时钟频率的提升就变得缓慢了,主要因为CPU 的功耗随时钟频率呈幂律增长,需要根据散热技术和制程工艺在性能与功耗间寻求平衡,既然CPU 时钟频率提升有限了,怎么继续按照摩尔定律提升性能呢?

    CPU 是用来处理计算任务的,想要在单位时间内处理更多的计算任务,除了提升单核心的时钟频率让其计算的更快之外,还可以增加CPU 核心数,让多个CPU 核心协同计算,CPU 开始往多核心方向发展,到2019年AMD EPYC 2 代已经达到64核心128线程了。为了充分发挥多核心CPU 的性能,操作系统和编程语言对并发执行的支持越来越好,各种编程语言也陆续提供了并发编程的函数库,比如C++11 就新增了并发编程的线程支持库。我们想要让多核CPU 更好的发挥性能,更高效的为我们的程序服务,掌握并发编程思想还是很有必要的。

    一、何为并发

    刚开始接触计算机编程语言时,我们编写一个程序,在main入口函数中调用其它的函数,计算机按我们设定的调用逻辑来执行指令获得结果。如果我们想在程序中完成多个任务,可以将每个任务实现为一个函数然后根据业务逻辑逐个调用。但如果我们想让多个任务几乎同时执行(时间间隔很小,我们感觉是同时执行的一样),比如一边放歌一边显示歌词,恐怕实现起来就会有明显的顿挫感(比如先播放一句歌声,然后显示一行歌词),影响交互体验。

    随着我们对计算性能的要求越来越高,多核心处理器很快普及流行。如果我们想让自己开发的程序更高效的运行,自然要充分发挥多核心处理器的优势。在多核心处理器上同时运行多个任务,比在单核心处理器上顺序执行多个任务高效的多。像单片机这种单核心处理器,在任务较多或者多个任务需要几乎同时执行时,也需要应用多任务并发编程提高对包括处理器在内的各硬件资源的利用效率。

    1.1 并发与并行

    说了这么多,那什么是并发呢?简单来说,并发指的是两个或多个独立的活动在同一时段内发生。并发在生活中随处可见:比如在跑步的时候同时听音乐,在看电脑显示器的同时敲击键盘等。

    与并发相近的另一个概念是并行。它们两者存在很大的差别,图示如下:

    • 并发:同一时间段内可以交替处理多个操作,强调同一时段内交替发生。
      并发
    • 并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生。
      并行

    1.2 硬件并发与任务切换

    既然并发是在同一时间段内交替发生即可,不要求同时发生,像单片机上的单核处理器也是可以支持并发多任务处理的,所以有单片机上跑的RTOS(Real-time operating system)诞生。单核心处理器上的多任务并发是靠任务切换实现的,跟多核处理器上的并行多任务处理还是有较大区别的,但对处理器的使用和多任务调度工作主要由操作系统完成了,所以我们在两者之间编写应用程序区别倒是不大。下面再贴个直观的图示:

    • 双核处理器并行执行(硬件并发)对比单核处理器并发执行(任务上下文切换

    并行与并发

    • 双核处理器均并发执行(一般任务数远大于处理器核心数,多核并发更常见)

    双核并发

    1.3 多线程并发与多进程并发

    前面一直在聊多任务并发,但计算机术语中用得更多的是线程与进程,三者的主要区别如下:

    • 任务:从我们认知角度抽象出来的一个概念,放到计算机上主要指由软件完成的一个活动。一个任务既可以是一个进程,也可以是一个线程。简而言之,它指的是一系列共同达到某一目的的操作。例如,读取数据并将数据放入内存中。这个任务可以作为一个进程来实现,也可以作为一个线程(或作为一个中断任务)来实现。
    • 进程:资源分配的基本单位,也可能作为调度运行的单位。可以把一个进程看成是一个独立的程序,在内存中有其完备的数据空间和代码空间。一个进程所拥有的数据和变量只属于它自己。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。
    • 线程:执行处理器调度的基本单位。一个进程由一个或多个线程构成,各线程共享相同的代码和全局数据,但各有其自己的堆栈。由于堆栈是每个线程一个,所以局部变量对每一线程来说是私有的。由于所有线程共享同样的代码和全局数据,它们比进程更紧密,比单独的进程间更趋向于相互作用,线程间的相互作用更容易些,因为它们本身就有某些供通信用的共享内存:进程的全局数据。

    由上面的定义可以看出,一个进程和一个线程最显著的区别是:线程有自己的全局数据。线程存在于进程中,因此一个进程的全局变量由所有的线程共享。由于线程共享同样的系统区域,操作系统分配给一个进程的资源对该进程的所有线程都是可用的,正如全局数据可供所有线程使用一样。

    在Mac、Windows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。在Linux系统中,线程的实现和进程并不特别区分,线程只不过是一种特殊的进程。多进程并发编程与多线程并发编程的区别主要在有没有共享数据,多进程间的通信较复杂且代价较大,主要的进程间通信渠道有管道、信号、文件、套接字等。由于C++没有提供进程间通信的原生支持,后续主要介绍多线程并发编程,和多线程间的同步与通信。
    并发进程与并发线程间通信
    多任务与多线程

    二、如何使用并发

    2.1 为什么使用并发

    在应用程序中使用并发的原因主要有两个:关注点分离和性能。事实上,我甚至可以说它们差不多是使用并发的唯一原因;当你观察的足够仔细时,一切其他因素都可以归结到这两者之一(或者可能是二者兼有)。

    • 关注点分离:通过将相关的代码放在一起并将无关的代码分开,可以使你的程序更容易理解和测试,从而减少出错的可能性。你可以使用并发来分隔不同的功能区域,即使在这些不同功能区域的操作需要在同一时刻发生的情况下;若不显式地使用并发,你要么被迫编写任务切换框架,要么在操作中主动地调用不相关的一段代码。
    • 更高效的性能:为了充分发挥多核心处理器的优势,使用并发将单个任务分成几部分且各自并行运行,从而降低总运行时间。根据任务分割方式的不同,又可以将其分为两大类:一类是对同样的数据应用不同的处理算法(任务并行);另一类是用同样的处理算法共同处理数据的几部分(数据并行)。

    知道何时不使用并发与知道何时使用它一样重要。基本上,不使用并发的唯一原因就是在收益比不上成本的时候。使用并发的代码在很多情况下难以理解,因此编写和维护的多线程代码就有直接的脑力成本,同时额外的复杂性也可能导致更多的错误。除非潜在的性能增益足够大或关注点分离地足够清晰,能抵消确保其正确所需的额外的开发时间以及与维护多线程代码相关的额外成本,否则不要使用并发。

    2.2 在C++中使用并发和多线程

    在早期的C++标准中,比如1998 C++标准版不承认线程的存在,并且各种语言要素的操作效果都以顺序抽象机的形式编写。不仅如此,内存模型也没有被正式定义,所以对于1998 C++标准,你没办法在缺少编译器相关扩展的情况下编写多线程应用程序。如果在之前想使用多线程并发编程,可以借助编译器厂商提供的平台相关的扩展多线程支持API(比如POSIX C和Microsoft Windows API),但这种多线程支持对平台依赖度较高,导致可移植性较差。

    为了解决平台相关多线程API使用上的问题,逐渐开发出了Boost、ACE等平台无关的多线程支持类库。直到C++11标准的发布,借鉴了很多Boost类库的经验,将多线程支持纳入C++标准库。C++11标准不仅提供了一个全新的线程感知内存模型,也包含了用于管理线程、保护共享数据、线程间同步操作以及低级原子操作的各个类。

    对于C++整体以及包含低级工具的C++类——特别是在新版C++线程库里的那些,参与高性能计算的开发者常常关注的一点就是效率。如果你正寻求极致的性能,那么理解与直接使用底层的低级工具相比,使用高级工具所带来的实现成本,是很重要的。这个成本就是抽象惩罚(abstraction penalty)。标准C++线程库在设计时,就非常注重高效的性能,提供了足够的低级工具(比如原子操作库),以付出尽可能低的抽象惩罚。C++标准库也提供了更高级别的抽象和工具,它们使得编写多线程代码更简单和不易出错。有时候运用这些工具确实会带来性能成本,因为必须执行额外的代码。但是这种性能成本并不一定意味着更高的抽象惩罚;总体来看,这种性能成本并不比通过手工编写等效的函数而招致的成本更高,同时编译器可能会很好地内联大部分额外的代码。

    三、C++线程创建

    一个多线程C++程序是什么样子的?它看上去和其他所有C++程序一样,通常是变量、类以及函数的组合。唯一真正的区别在于某些函数可以并发运行,所以你需要确保共享数据的并发访问是安全的。当然,为了并发地运行函数,必须使用特定的函数以及对象来管理各个线程。

    3.1 C++11新标准多线程支持库

    • < thread > : 提供线程创建及管理的函数或类接口;
    • < mutex > : 为线程提供获得独占式资源访问能力的互斥算法,保证多个线程对共享资源的同步访问;
    • < condition_variable > : 允许一定量的线程等待(可以定时)被另一线程唤醒,然后再继续执行;
    • < future > : 提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常;
    • < atomic > : 为细粒度的原子操作(不能被处理器拆分处理的操作)提供组件,允许无锁并发编程。

    3.2 线程创建的简单示例

    线程创建和管理的函数或类主要由< thread >库文件来提供,该库文件的主要操作如下:
    thread库文件
    由上表可知,通过std::thread t(f, args…)创建线程,可以给线程函数传递参数。通过join()函数关联并阻塞线程,等待该线程执行完毕后继续;通过detach()函数解除关联使线程可以与主线程并发执行,但若主线程执行完毕退出后,detach()接触关联的线程即便没有执行完毕,也将自动退出,有时可能这并非我们预期的结果,所以需要特别注意。下面给出一段线程管理的示例代码:

    //thread1.cpp  创建线程,并观察线程的并发执行与阻塞等待
    
    #include <iostream>
    #include <thread>
    #include <chrono>
    
    using namespace std;
     
    void thread_function(int n)
    {
        std::thread::id this_id = std::this_thread::get_id();			//获取线程ID
    
        for(int i = 0; i < 5; i++){    
            cout << "Child function thread " << this_id<< " running : " << i+1 << endl;
            std::this_thread::sleep_for(std::chrono::seconds(n));   	//进程睡眠n秒
        }
    }
    
    class Thread_functor
    {
    public:
        // functor行为类似函数,C++中的仿函数是通过在类中重载()运算符实现,使你可以像使用函数一样来创建类的对象
        void operator()(int n)
        {
            std::thread::id this_id = std::this_thread::get_id();
    
            for(int i = 0; i < 5; i++){
                cout << "Child functor thread " << this_id << " running: " << i+1 << endl;
                std::this_thread::sleep_for(std::chrono::seconds(n));   //进程睡眠n秒
            }
        }	
    };
    
     
    int main()
    {
        thread mythread1(thread_function, 1);      // 传递初始函数作为线程的参数
        if(mythread1.joinable())                  //判断是否可以成功使用join()或者detach(),返回true则可以,返回false则不可以
            mythread1.join();                     // 使用join()函数阻塞主线程直至子线程执行完毕
        
        Thread_functor thread_functor;			 //函数对象实例化一个对象
        thread mythread2(thread_functor, 3);     // 传递初始函数作为线程的参数
        if(mythread2.joinable())
            mythread2.detach();                  // 使用detach()函数让子线程和主线程并行运行,主线程也不再等待子线程
    
        auto thread_lambda = [](int n){			//lambda表达式格式:[capture list] (params list) mutable exception-> return type { function body }
            std::thread::id this_id = std::this_thread::get_id();
            for(int i = 0; i < 5; i++)
            {
                cout << "Child lambda thread " << this_id << " running: " << i+1 << endl;
                std::this_thread::sleep_for(std::chrono::seconds(n));   //进程睡眠n秒
            }       
        };
    
        thread mythread3(thread_lambda, 4);     // 传递初始函数作为线程的参数
        if(mythread3.joinable())
            mythread3.join();                     // 使用join()函数阻塞主线程直至子线程执行完毕
    
        std::thread::id this_id = std::this_thread::get_id();
        for(int i = 0; i < 5; i++){
            cout << "Main thread " << this_id << " running: " << i+1 << endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    
        getchar();
        return 0;
    }
    

    使用GCC编译为可执行程序的命令如下:

    g++ -Wall -g -std=c++11 -pthread thread1.cpp -o thread1		
    # -Wall显示所有警告,-g输出调试信息,-std=c++11使用c++11标准编译,-pthread编译使用POSIX thread库文件
    

    线程创建的参数是函数对象,函数对象不止是函数指针或成员函数指针,同时还包括函数对象(仿函数)与lambda表达式。上面的代码分别用三种函数对象创建了三个线程,其中第一个线程mythread1阻塞等待其执行完后继续往下执行,第二个线程mythread2不阻塞等待在后台与后面的第三个线程mythread3并发执行,第三个线程继续阻塞等待其完成后再继续往下执行主线程任务。

    为了便于观察并发过程,对三个线程均用了睡眠延时this_thread::sleep_for(duration)函数,且延时时间作为参数传递给该函数。这里的参数是支持C++泛型模板的,STL标准容器类型(比如Array/Vector/Deque/List/Set/Map/String等)都可以作为参数传递,但这里的参数默认是以拷贝的方式传递参数的,当期望传入一个引用时,要使用std::ref进行转换。

    针对任何线程(包括主线程),< thread > 还声明了一个命名空间std::this_thread,用以提高线程专属的全局函数。函数声明和效果见下表:
    this_thread命名空间
    上面的代码就是利用了std::this_thread提供的函数获得当前线程的ID,让当前线程睡眠一段时间(一般需要< chrono >头文件提供duration或timepoint)的功能,代码执行结果如下图所示:
    thread执行结果

    上面的示例假如多重复运行几次,有很大可能会出现某行与其他行交叠错乱的情况(如下图所示),为何会出现这种情况呢?这就涉及到多线程资源竞争的问题了,即一个线程对某一资源(这里指显示终端)的访问还未完成,另一线程抢夺并访问了该资源,导致该资源数据混乱情况的出现。解决方案详见下一篇文章:C++多线程并发(二)—线程同步
    行间交错

    更多文章:

    展开全文
  • 多线程并发服务器编程

    万次阅读 2011-12-12 09:47:45
    编写多线程并发服务器程序和客户程序,具体功能如下: 1 、服务器等待接收客户的连接请求,一旦连接成功则显示客户地址,接着接收客户端的名称并显示;然后接收来自该客户的字符串,每当收到一个字符串时,...
    
    

    多线程并发服务器编程

    一、实验目的

    理解线程的创建和终止方法;

    学会编写基本的多线程并发服务器程序和客户程序;

    理解多线程与多进程的区别。

    二、实验平台

    ubuntu-8.04操作系统

    三、实验内容

    编写多线程并发服务器程序和客户程序,具体功能如下:

    1、服务器等待接收客户的连接请求,一旦连接成功则显示客户地址,接着接收客户端的名称并显示;然后接收来自该客户的字符串,每当收到一个字符串时,显示该字符串,并将字符串按照恺撒密码的加密方式(K=3)进行加密,再将加密后的字符发回客户端;之后,继续等待接收该客户的信息,直到客户关闭连接。要求服务器具有同时处理多个客户请求的能力。

    2、客户首先与相应的服务器建立连接;接着接收用户输入的客户端名称,并将其发送给服务器;然后继续接收用户输入的字符串,再将字符串发送给服务器,同时接收服务器发回的加密后的字符串并显示。之后,继续等待用户输入字符串,直到用户输入Ctrl+D,客户关闭连接并退出。

    四、实验原理

    多进程方式使用fork生成子进程存在一些问题。首先,fork占用大量的资源,内存映像要从父进程拷贝到子进程,所有描述符要在子进程中复制;其次,fork子进程后,需要用进程间通信在父进程和子进程间传递信息,从子进程返回信息给父进程需要做较多的工作。多线程有助于解决以上两个问题。

    线程是进程内的独立执行实体和调度单元,又称为轻量级进程(lightwightprocess);创建线程比进程快10~100倍。一个进程内的所有线程共享相同的内存空间、全局变量等信息(这种机制又带来了同步问题)。


    1pthread_create()函数

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

    -------------------------------------------------------------------
    #include<pthread.h>

    intpthread_create(pthread_t *tid, const pthread_attr_t *attr, void*(*func)(void *), void *arg);

    返回:成功时为0;出错时非0

    -------------------------------------------------------------------

    新线程由线程id标识:tid,新线程的属性attr包括:优先级、初始栈大小、是否应该是守护线程等等。线程的执行函数和调用参数分别是:funcarg

    由于线程的执行函数的参数和返回值类型均为void*,因此可传递和返回指向任何类型的指针。

    常见的返回错误值:

    EAGAIN:超过了系统线程数目的限制。

    ENOMEN:没有足够的内存产生新的线程。

    EINVAL:无效的属性attr值。


    2pthread_join()函数

    pthread_join()函数用于等待一个线程终止。

    -------------------------------------------------------------------
    #inlcude<pthread.h>

    intpthread_join(pthread_t tid, void **status);

    返回:成功时为0;出错时返回正的错误码。

    -------------------------------------------------------------------

    该函数类似与waitpid函数,但必须指定等待线程的ID(由参数tid指定),该函数不能等待任意一个线程结束。


    3pthread_detach()函数

    pthread_detach()函数将指定的线程变成脱离的。

    -------------------------------------------------------------------
    #include<pthread.h>

    intpthread_detach(pthread_t tid)

    返回:成功时为0;出错时返回错误码。

    -------------------------------------------------------------------

    线程或者是可汇合的(joinable)(默认),或者是脱离的(detached)。当可汇合的线程终止时,其线程id和退出状态将保留,直到另外一个线程调用pthread_join。脱离的线程则像守护进程,当它终止时,释放所有资源,我们不能等待它终止。

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

    4pthread_self()函数

    每一个线程都有一个IDpthread_self()函数返回自己的线程ID

    -------------------------------------------------------------------
    #include<pthread.h>

    pthread_tpthread_self(void);

    返回:调用线程的线程id

    -------------------------------------------------------------------

    线程可以通过如下语句将自己设置为可脱离的:

    pthread_detachpthread_self());

    5pthread_exit()函数

    pthread_exit()函数用于终止当前线程,并返回状态值。如果当前线程是可汇合的,将保留线程id和退出状态供pthread_join()函数调用。

    -------------------------------------------------------------------
    #include<pthread.h>

    void pthread_exit(void*status);

    无返回值;

    -------------------------------------------------------------------

    指针status:指向线程的退出状态。不能指向一个局部变量,因为线程终止时其所有的局部变量将被撤销。

    还有其他两种方法可使线程终止:

    1)启动线程的函数pthread_create()的第3个参数返回。其返回值便是线程的终止状态;

    2)如果进程的main()函数返回,或者当前进程中,任一线程调用了exit()函数,将终止该进程中所有线程。


    五、实验步骤

    1、登陆进入ubuntu操作系统,新建一个文件,命名为mthread_server.c

    2、在mthread_server.c中编写相应代码并保存,作为服务器端程序。客户端程序代码同mproc_client.c一致。blog:http://blog.csdn.net/yueguanghaidao/article/details/7060350

    3、打开一个终端,执行命令进入mthread_server.cmproc_client.c所在目录。

    4、执行命令gccomthread_servermthread_server.c-lpthread生成可执行文件mthread_server

    5、执行命令./mthread_server,运行服务器端。

    6、打开第2终端,执行命令进入mthread_server.cmproc_client.c所在目录。

    7、执行命令./mproc_client127.0.0.1,模拟客户1

    8、打开第3终端,执行命令进入mthread_server.cmproc_client.c所在目录。

    9、执行命令./mproc_client127.0.0.1,模拟客户2

    10、程序运行结果如下:


    服务器端:


    客户1



    客户2


    11、在客户端按下Ctrl+D,关闭客户连接。

    12、认真分析源代码,体会多线程并发服务器程序的编写。

    六、参考程序(mthread_server.c

    #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;
    };
    
    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);
    }


    展开全文
  • Java多线程并发协同

    千次阅读 2018-08-02 22:51:26
    Java并发相关技术:线程池、线程安全(锁)、多线程并发协同、并发集合类、原子类。 什么是并发协同? 多个线程并发,协同来完成一件事情的过程中,因事情处理的需要,需控制某些线程阻塞,等待另一些线程完成某...
  • python 多进程并发与多线程并发总结

    万次阅读 2015-05-16 19:33:18
    本文对python支持的几种并发方式——多进程并发与多线程并发进行简单的总结。
  • 高性能、高并发TCP服务器(多线程调用libevent)

    万次阅读 热门讨论 2012-05-22 09:58:14
    主线程只处理监听客户端的连接请求,并将请求平均分配给子线程。 子线程处理与客户端的连接以及相关业务。 每个子线程有一个“连接”队列。每个“连接”有一个“反馈”队列。   先上个流程图,要上班了,以后再...
  • 1,java在执行静态方法时,会在内存中拷贝一份,如果静态方法所在的类里面没有静态的变量,那么线程访问就是安全的,比如在javaee中服务器必然会多线程的处理请求此时如果设计全局需要调用的静态方法,可用此种设计...
  • 多线程并发和并行的区别

    千次阅读 2020-09-13 14:35:34
    背景  对于java开发从业人员来说,并发编程是绕不开的话题,juc并发包下提供了一系列多线程场景解决方案。  随着jdk1.8的普及,多线程处理... 多线程并发和并行的区别   2. parallelStream()并行滥用的后果   3
  • python 多线程并发

    千次阅读 2016-09-08 10:40:47
    python 多线程并发
  • .net 的 HttpWebRequest 或者 WebClient 在多线程情况下存在并发连接限制,这个限制在桌面操作系统如 windows xp , windows 7 下默认是2,在服务器操作系统上默认为10. 如果不修改这个并发连接限制,那么客户端同时...
  • [TOC]前言多线程并发这块的东西很重要,同样也很难,虽然是在J2SE中学习的知识,但是也只会基本的启动,了解基本的生命周期而已,并发这一块的东西基本还是不会,但是大部分公司都要求会并发,一般工作两三年的...
  • 如下通过一组对比例子从头讲解: 在多线程中使用静态方法会发生什么事?也就是说多线程访问同一个类的static静态方法会发生什么事?是否会发生线程安全问题?public class Test { public static vo
  • 多线程并发 (二) 了解 Thread

    千次阅读 多人点赞 2019-12-30 14:46:20
    章节:多线程并发 (一) 了解 Java 虚拟机 - JVM多线程并发 (二) 了解 Thread多线程并发 (三) 锁 synchronized、volatile多线程并发 (四) 了解原子类 AtomicXX 属性地址偏移量,CAS机制多线程并发 (五) ReentrantLock...
  • 在前一篇文章《C++多线程并发编程(一)—线程管理》中解释多线程并发时说到两个比较重要的概念: 多线程并发:在同一时间段内交替处理多个操作,线程切换时间片是很短的(一般为毫秒级),一个时间片多数时候...
  • java高并发之有返回值的多线程并发

    千次阅读 2017-05-27 11:49:40
    需求: 工作流策略的一个节点,需要查询13个第三方的...以下是实现参考的例子JAVA多线程实现的四种方式Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Th
  • 多线程并发之Semaphore(信号量)使用详解

    万次阅读 多人点赞 2019-01-02 17:04:03
    多线程并发之CountDownLatch(闭锁)使用详解 多线程并发之显示锁Lock与其通信方式Condition源码解读 多线程并发之读写锁(ReentranReadWriteLock&amp;amp;amp;amp;ReadWriteLock)使用详解 多线程并发之线程池...
  • 输出的线程名显示是多线程调用,但是打断点之后所有的请求都阻塞了,是单线程还是多线程呢?在这个地方需要考虑并发问题吗?
  • sqlite3 多线程 并发

    千次阅读 2013-12-05 16:26:03
    前段时间做SQLITE3的增删改,需要考虑接口被多线程调用,于是看了下SQLITE3 的 Help Doc , 发现有些函数 (sqlite3_open_v2(), sqlite3_prepare_v2())是用来进行多线程的但是没看明白(英语不好啊,弱暴了),并且低版本的...
  • SQLite多线程并发操作

    千次阅读 2018-11-27 15:11:07
    SQLite多线程并发操作 先理清楚一个概念 多线程操作和多数据库连接操作是不同的概念 多线程操作:多线程操作数据库时可以使用一个数据库连接,也可以每个线程使用各自的数据库连接 多数据库连接操作:即每个...
  • java多线程并发机制

    千次阅读 2017-04-27 10:59:33
    一、多线程 1、操作系统有两个容易混淆的概念,进程和线程。 进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种...
  • c#多线程并发执行一个操作函数

    万次阅读 2013-05-13 16:41:08
    有时候我们进行很多的数据对比运算时,单线程的程序显得很慢,这个时候可以用多线程并发运行: int maxThread = 10; //10个并发线程 int currTNum = 0; WaitHandle[] whs = new WaitHandle...
  • Java 多线程 并发编程

    万次阅读 多人点赞 2013-08-28 01:42:48
    一、多线程 1、操作系统有两个容易混淆的概念,进程和线程。 进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种...
  • Disruptor多线程并发构架

    千次阅读 2017-05-26 15:09:51
    这个系统是建立在JVM平台上,其核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单。业务逻辑处理器完全是运行在内存中,使用事件源驱动方式。业务逻辑处理器的核心是Disruptor。 Disruptor它是一个...
  • java有返回值的多线程并发

    千次阅读 2019-03-20 10:32:34
    Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。 其中前两种方式线程执行...
  • 对JAVA多线程 并发编程的理解

    千次阅读 2015-03-22 00:29:40
    对JAVA多线程并发编程的理解 Java多线程编程关注的焦点主要是对单一资源的并发访问,本文从Java如何实现支持并发访问的角度,浅析对并发编程的理解,也算是对前段时间所学的一个总结。 线程状态转换 Java语言定义...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 441,881
精华内容 176,752
关键字:

如何创建多线程并发调用服务