APUE编程之多线程服务器的开发学习

APUE编程之多线程服务器的开发学习

1、 在操作系统原理的术语中,线程是进程的一条执行路径。线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,所有的线程都是在同一进程空间运行,这也意味着多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。
但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境 (register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。


```handlebars
2、线程的创建

一个进程创建后,会首先生成一个缺省的线程,通常称这个线程为主线程(或称控制线程),C/C++程序中,主线程就是通过 main函数进入的线程,由主线程调用pthread_create()创建的线程称为子线程,子线程也可以有自己的入口函数,该函数由用户 在创建的时候指定。每个线程都有自己的线程ID,可以通过pthread_self()函数获取。最常见的线程模型中,除主线程较为特殊 之外,其他线程一旦被创建,相互之间就是对等关系,不存在隐含的层次关系。每个进程可创建的最大线程数由具体实现决定。

无论在windows中还是Posix中,主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有 子线程执行都会终止。这时整个进程结束或僵死,部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁 后销毁,这时进程处于僵死状态。线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但是为线程分配的系统资 源不一定释放,可能在系统重启之前,一直都不能释放,终止态的线程,仍旧作为一个线程实体存在于操作系统中,什么时候销 毁,取决于线程属性。在这种情况下,主线程和子线程通常定义以下两种关系:
1. 可会合(joinable):
这种关系下,主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线 程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。在主线程的线程函 数内部调用子线程对象的wait函数实现,即使子线程能够在主线程之前执行完毕,进入终止态,也必须执行会合操作,否 则,系统永远不会主动销毁线程,分配给该线程的系统资源也永远不会释放。
2. 相分离(detached):表示子线程无需和主线程会合,也就是相分离的,这种情况下,子线程一旦进入终止状态,这种 方式常用在线程数较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是 很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。

线程的分离状态决定一个线程以什么样的方式来终止自己,在默认的情况下,线程是非分离状态的,这种情况下,原有的线程 等待创建的线程结束,只有当pthread_join函数返回时,创建的线程才算终止,释放自己占用的系统资源,而分离线程没有被其 他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。

3、互斥锁

当线程创建之后,最大的问题就是临界资源和临界区的互斥访问,例如洗手间就是临界资源,我们在进入到洗手间(临界区)后,就首先上锁; 然后用完离开洗手间(临界区)之后,把锁释放供别人使用。如果有人想去洗手间时发现门锁上了,他也有两种策略:
1、在洗手间那里等(阻塞);
2、暂时先离开等会再过来看(非阻塞);

 4、死锁
如果多个线程要调用多个对象,则在上锁的时候可能会出现“死锁”。举个例子: A、B两个线程会同时使用到两个共享变量 m和n,同时每个变量都有自己相应的锁M和N。 这时A线程首先拿到M锁访问m,接下来他需要拿N锁来访问变量n;  而如果此 时B线程拿着N锁等待着M锁的话,就造成了线程“死锁”。
死锁产生的4个必要条件   

1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结 束。
2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU 的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。

产生死锁需要四个条件,那么,只要这四个条件中至少有一个条件得不到满足,就不可能发生死锁了。由于互斥条件是非共享 资源所必须的,不仅不能改变,还应加以保证,所以,主要是破坏产生死锁的其他三个条件。

a、破坏“占有且等待”条件    
方法1:所有的进程在开始运行之前,必须一次性地申请其在整个运行过程中所需要的全部资源。
优点:简单易实施且安全。
  缺点:因为某项资源不满足,进程无法启动,而其他已经满足了的资源也不会得到利用,
  严重降低了资源的利用率,造成 资源浪费。使进程经常发生饥饿现象。  
方法2:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资源,
便开始运行,在运行过程中逐步释放掉分配到 的已经使用完毕的资源,然后再去请求新的资源。
这样的话,资源的利用率会得到提高,也会减少进程的饥饿问题。
b、破坏“不可抢占”条件      

当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用 的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。该种方法实现起来比较复杂,且代价也比 较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这 不仅会延长进程的周转周期,还会影响系统的吞吐量。

c、破坏“循环等待”条件  

可以通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只 能申请编号大于i的资源。

4、多线程服务器编程代码:



#ifndef __SOCKET_SERVER_H //防止重重定义
#define __SOCKET_SERVER_H
#include <sys/types.h>      
#include <sys/socket.h>
#include <string.h>
#include<stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <getopt.h>
#include <pthread.h>
#include <ctype.h>
#define	MSG_STR  "Hello network word! I'm sever!"
#define	BUF_SIZE  1024
#endif

typedef void *(THREAD_BODY) (void *thread_arg); //定义函数指针
void *thread_worker(void *ctx);
int thread_start(pthread_t * thraed_id, THREAD_BODY * thread_workbody, void * thread_arg);

void print_usage(char *prograname)
{
    printf("%s usage : \n", prograname);
    printf("-p(--port): specify sever listen port.\n");
    printf("-h(--help): print this help information.\n");

    return  ;

}
int main (int argc, char **argv)
{

    int                      sockfd;
    int                      rv = -1;
    int                      clifd;
    struct sockaddr_in       servaddr;
    struct sockaddr_in       cliaddr;
    socklen_t                len = sizeof(cliaddr);
    int                      port    = 0;
    int                      ch;
    int                      on = 1;
    pthread_t                tid;

    struct option        opts[] = {
        {"port", required_argument, NULL, 'p'},            
        {"help", no_argument, NULL, 'h'},          
        {NULL, 0, NULL, 0}

    };

    while((ch=getopt_long(argc, argv, "p:h", opts, NULL)) != -1 )  //实现参数解析
    {
        switch(ch)
        {
            case 'p':
                port=atoi(optarg);
                break;
            case 'h':
                print_usage(argv[0]);
                return 0;
        }           
    }

    if( !port )        
    {
        print_usage(argv[0]);
        return 0;        
    }

    sockfd = socket(AF_INET,SOCK_STREAM,0); //服务器第一步,socket();
    if(sockfd < 0)
    {
        printf("Cearte socket failure :%s\a\n",strerror(errno));
        return -1;
    }
    printf("Create socket[%d] sucessfully.\n",sockfd);

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on,sizeof(on));//使得端口号在短时间内可以得到调用

    memset(&servaddr,0,sizeof(servaddr)); //清空servaddr结构体
    servaddr.sin_family = AF_INET;    //设置ipv4协议,如果要设置ipv6协议, 则需AF_INET6;
    servaddr.sin_port = htons(port);   //将本地字节序的端口转化为网络字节序
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //可以监听任何访问IP地址

    rv = bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); //服务器第二步,bind();
    if(rv < 0 )
    {
        printf(" socket[%d] bund port[%d] failure :%s\n\a",sockfd, port,strerror(errno));
        return -2;
    }

    listen(sockfd,13);  //服务器第三步,listen;
    printf("Statring to listen on port[%d].\n",port);

    while(1)
    {
        printf("Starting to accept new client incomming...\n");

        clifd = accept(sockfd, (struct sockaddr *)&cliaddr,&len); //服务器第四步,accept(),本处会从在阻塞
        if(clifd < 0)
        {
            printf("Accept new client failure :%s\n\a", strerror(errno));
        }
        printf("Accept new client[%s:%d]successfuly\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));

        thread_start(&tid, thread_worker,(void *)(long)clifd ); //创建新的线程来解决accept阻塞问题
    }
    close(sockfd);

    return 0;
}

int thread_start(pthread_t * thread_id, THREAD_BODY * thread_workbody, void * thread_arg)
{
    int                rv = -1;
    pthread_attr_t      thread_attr;
    if(pthread_attr_init(&thread_attr))  //初始化线程属性 thread_attr
    {
        printf("pthread_attr_init() failure:%s\n", strerror(errno));
        goto CleadUp;
    }

    if(pthread_attr_setstacksize(&thread_attr, 120*1024)) //设置栈大小
    {
        printf("pthread_attr_setstacksize() failure:%s\n", strerror(errno));
        goto CleadUp;   
    }

    if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED)) //设置可分离的状态
    {
        printf("pthread_attr_detachstate() failure:%s\n", strerror(errno));
        goto CleadUp;   
    }

    if(pthread_create(thread_id, &thread_attr, thread_workbody, thread_arg)) //创建子线程
    {
        printf("pthread_create() failure:%s\n", strerror(errno));
        goto CleadUp;   
    }

    rv = 0;
CleadUp:
    pthread_attr_destroy(&thread_attr); //摧毁线程属性,释放占用资源
    return rv;
}

void *thread_worker( void *ctx) //服务器第五步 read()会阻塞, write();
{ 
    int        clifd = 0;
    char       buf[1024];
    int        rv = 0;
    if(!ctx) 
    {
        printf("Invalid input arguements in %s()\n", __FUNCTION__);
        pthread_exit(NULL);   
    }
    clifd = (long)ctx;
    printf("clifd = %d\n",clifd );
    printf("Child thread start to commuicate with socket client ...\n");
    while (1) 
    {
        memset(buf,0,sizeof(buf));
        rv = read(clifd,buf,sizeof(buf));
        if(rv < 0)
        {
            printf("Read from client socket[%d] failure: %s\n",clifd,strerror(errno));
            close(clifd);
            pthread_exit(NULL);
        }
        else if(rv == 0)
        {

            printf("socket[%d] get disconnected\n",clifd);
            close(clifd);
            pthread_exit(NULL); //子线程退出必须调用pthread_exit(),调用exit()或return 都会使父子线程都结束
        }
        else if(rv >0)
        {
            printf("Read %d bytes data form client:\n%s\n",rv,buf);
        }
        rv = write(clifd,MSG_STR,strlen(MSG_STR));
        if(rv < 0)
        {
            printf("write to client by socket[%d] failure :%s.\a\n",clifd,strerror(errno));
            close(clifd);
            pthread_exit(NULL);
        }
    }
}
发布了16 篇原创文章 · 获赞 9 · 访问量 791

猜你喜欢

转载自blog.csdn.net/qq_44045338/article/details/104280170