基于Linux下的socket网络编程-----------------多线程

1.什么叫线程

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,那就是程序本身。

2.线程池

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。线程池中的线程由系统管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。

3.创建线程

一个进程创建后,会首先产生一个缺省的线程,通常这个线程称为主线程或称控制线程。C/C++程序中,主线程就是通过main函数进入的线程,然后主线程调用pthread_create()创建的线程称为子线程,子线程也有自己的入口函数,该函数是由用户创建的时候指定的。每个线程都有自己的线程ID,可以通过pthread_self()函数获取。
无论在windows还是在Posix中,主线程和子线程默认的关系是:无论子线程执行完毕与否,只要主线程执行程序完毕退出,子线程自然跟着退出。这时整个进程结束或僵死,部分线程保持一种终止却没有销毁的状态,而进程只有在所有线程销毁后才能销毁,这时的进程属于僵死状态。线程函数执行完毕退出,或以其他非常方式退出,线程进入终止状态,但是为线程分配的系统资源不一定释放,可能在系统重启之前,一直不能释放,终止态的线程,仍旧最为一个线程实体存在于操作系统中,什么时候销毁,取决于线程属性。在这种情况下,主线程和子线程通常定义以下两种关系:

  1. 可会合(joinable):在这种关系下,主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。在主线程的线程函数内部调用子线程的对象的wait函数实现,即使子线程能够在主线程之前执行完毕,进入终止态,也必须和主线程会合,否则系统永远不会主动销毁线程,分配给该线程的资源也不会释放。
  2. 相分离(detached):表示子线程无需与主线程会合,也就是相分离,在这种情况下,子线程一旦进入终止状态,这种方式常用在线程较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。

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

pthread_create()用来创建线程,其函数原型为:

#include<pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.

临界资源:如果一个资源会被不同的线程访问修改,那么我们把这个资源叫做临界资源。
临界区:那么对于该资源访问修改的相关代码就叫做临界区。

4.互斥锁

当两个或多个线程对临界资源都要使用的时候,这时就会产生冲突,那么改变这一冲突的便是锁的机制。当某一线程占用某一临界资源的时候,便自然的对临界区进行“上锁”操作,等这一线程占用这资源完毕后便释放锁,把锁释放给别人使用。如果另外的线程要使用这一资源,但发现上锁了,这时他有两种策略:阻塞和非阻塞。

下面我们通过代码来了解锁的机制:

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

void *thread_worker1(void *args)
void *thread_worker2(void *args)

typedef struct worker_ctx_s
{
     int           shared_var;
     pthread_mutex_t  lock;
}worker_ctx_t;

int main(int argc, char **argv)
{
      worker_ctx_t            worker_ctx;
      pthread_t               tid;
      pthread_attr_t          thread_attr;
      worker_ctx.shared_var = 100;
      pthread_mutex_init(&worker_ctx.lock, NULL);

      if ( pthread_attr_init(&thread_attr))
      {
          printf("pthread failture:%s\n", strerror(errno));
          return -1;
      }
      if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED))
      {
         printf("pthread failture:%s\n", strerror(errno));
         return -1;
      }
      if(pthread_attr_setstacksize(&thread_attr, 120*1024))
      {
          printf("failture:%s\n", strerror(errno));
          return -1;
      }
      pthread_create(&tid, &thread_attr, thread_worker1,
 &worker_ctx);
      printf("Thread worker1 tid[%d] created ok\n", tid);
      pthread_create(&tid, &thread_attr, thread_worker2, &worker_ctx);
      printf("Thread worker2 tid[%d] created ok\n", tid);

      while(1)
      {
          printf("Main/Control thread shared_var:%d\n", worker_ctx.shared_var);
          sleep(10);
      }
      pthread_mutex_destroy(&worker_ctx.lock)}

void *thread_worker1(void *args)
{
    worker_ctx_t      *ctx = (worker_ctx_t *)args;
    if(!args)
    {
         printf("%s() get invalid arguments\n",_FUNCTION_);
         pthread_exit(NULL);
    }
    printf("Thread worker1 [%d] start running...\n",pthread_self());
    while(1)
    {
        pthread_mutex_lock(&ctx->lock);
        printf("+++:%s before shared_var++:%d\n",_FUNCTION_, ctx->shared_var);
        ctx->shared_var ++;
        sleep(2);
        printf("+++:%s after sleep shared shared_var:%d\n", _FUNCTION_, ctx->shared_var);
        pthread_mutex_unlock(&ctx->lock);
        sleep(1);

    }
    printf("Thread worker 1 exit...\n");
    return NULL;
}

void *thread_worker2(void *args)
{
    worker_ctx_t       *ctx = (worker_ctx_t *)args;
    if(!args)
    {
        printf("%s() get invalid arguments\n"_FUNCTION_);
        pthread_exit(NULL);
    }
    printf("Thread worker 2 [%d] start running...\n",pthread_self());
    while(1)
    {
       if(0 != pthread_mutex_trylock(&ctx->lock) )
       {
          continue;
       }
       printf("---:%s before shared_var++:%d\n", _FUNCTION_, ctx->shared_var);
       ctx->shared_var ++;
       sleep(2);
       printf("---:%s after sleep shared_var:%d\n", _FUNCTION_, ctx->shared_var);
       pthread_mutex_unlock(&ctx->lock);
       sleep(1);
    }
    printf("Thread worker 2 exit...\n");
    return NULL;
}

代码分析:
代码的11~15行:因为在创建线程给线程线程执行函数传参的时候只能传一个参数,而我们要传递共享的变量shared_var和它相应的互斥锁lock,所以在这里需要用结构体(worker_ctx_t, ctx:context)将它们封装在一块传进去。
代码19行:使用work_ctx_t结构体类型定义了传给子线程的变量参数;
代码24行:互斥锁在使用之前,需要先调用pthread_mutex_init()函数来初始化互斥锁;
代码48行:在创建第二个线程时也设置了分离属性, 这时主线程后面的while(1)循环就会执行了;
代码57行:互斥锁在使用完之后,我们应该调用pthread_mutex_destroy()将他摧毁释放;
代码74行:这里调用pthread_mutex_lock()来申请锁, 这里是阻塞锁,如果锁被别的线程持有则该函数不会返回;
代码81行:在访问临界资源完成退出临界区时,我们调用pthread_mutex_unlock来释放锁,这样其他线程才能再次访问;
代码105行:第二行线程我们使用pthread_mutex_trylock来申请锁,这里使用的是非阻塞锁;如果锁现在被别的线程占用则返回非0值,如果没有被占用则返回0;
代码83行、117行:这里都要加上延时, 否则一个线程拿到锁之后会一直占有改锁;另外一个线程则不能获取到锁;

代码运行展示:

在这里插入图片描述

5.死锁

如果多个线程要调用多个对象,则在上锁的时候可能会出现“死锁”.举个例子: 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的资源

6.多线程改写服务器

多线程编程模型和多进程编程模型的工作方式完全一样,在了解Linux下多线程编程之后,我们就可以使用多线程编程模型改写服务器的多线程实现,流程图如下:
在这里插入图片描述
代码如下:

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<getopt.h>
#include<pthread.h>
#include<ctype.h>

typedef void *(THREAD_BODY)(void *thread_arg);

void *thread_worker(void *ctx);
int thread_start(pthread_t *thread_id, THREAD_BODY * thread_workerbody, void *thread_arg);

void print_usage(char *progname)
{
    printf("%s usage:\n", progname);
    printf("-p(--port):sepcify server listen port.\n");
    printf("-h(--help):print this help information.\n");
    return;
}

int main(int argc, char **argv)
{
     int                   sockfd = -1;
     int                   rv = -1;
     struct sockaddr_in    servaddr;
     struct sockaddr_in    cliaddr;
     socklen_t             len;
     int                   port = 0;
     int                   clifd;
     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);
     if(sockfd < 0)
     {
         printf("failture:%s\n", strerror(errno));
         return -1;
     }
     printf("Creat socket[%d] successfully!\n", sockfd);
     setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
     memset(&servaddr, 0, sizeof(servaddr));
     servaddr.sin_family = AF_INET;
     servaddr.sin_port = htons(port);
     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

     rv = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
     if(rv<0)
     {
          printf("failture:%s\n", strerror(errno));
          return -2;
     }

     listen(sockfd, 13);
     printf("Start to listen on port [%d]\n", port);

     while(1)
     {
          printf("Start accept new client incoming...\n");
          clifd=accept(sockfd, (struct sockaddr *)&cliaddr, &len);
          if(clifd < 0)
          {
              printf("Accept new client failture:%s\n",strerror(errno));
              continue;
          }
          printf("Accept new client[%s:%d] successfully\n", inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
          thread_start(&tid, thread_worker, (void *)clifd);
     }
     close(sockfd);
     return 0;
}

int thread_start(pthread_t *thread_id, THREAD_BODY * thread_workerbody, void *thread_arg)
{
    int                 rv = -1;
    pthread_attr_t      thread_attr;
    if(pthread_attr_init(&thread_attr))
    {
         printf("failture:%s\n", strerror(errno));
         goto Cleanup;
    }
    if(pthread_attr_setstacksize(&thread_attr, 120*1024))
    {
         printf("failture:%s\n", strerror(errno));
         goto Cleanup;
    }
    if(pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED))
    {
       printf("failture:%s\n", strerror(errno));
       goto Cleanup;
    }
    if(pthread_create(thread_id, &thread_attr, thread_workbody, thread_arg))
    {
         printf("failture:%s\n", strerorr(errno));
         goto Cleanup;
    }
    rv = 0;
Cleanup:
        pthread_attr_destroy(&thread_attr);
        return rv;
}

void *thread_worker(void *ctx)
{
     int                   clifd;
     int                   rv;
     char                  buf[1024];
     int                   i;

     if(!ctx)
     {
         printf("Invalid input atgument in %s()\n", _FUNCTION_);
         pthread_exit(NULL);
     }
     clifd = (int)ctx;
     printf("Child thread start to communicate with socket client...\n");
     while(1)
     {
         memset(buf, 0, sizeof(buf));
         rv=read(clifd, buf, sizeof(buf));
         if(rv<0)
         {
              printf("Read data failture:%s\n", strerror(errno));
              close(clifd);
              pthread_exit(NULL);
         }
         else if(rv == 0)
         {
             printf("Socket [%d] get disconnect and thread will exit.\n", clifd);
             close(clifd);
             pthread_exit(NULL);
         }
         else if(rv>0)
         {
             printf("Read %d bytes data from server:%s\n", rv, buf);
         }
         for(i = 0; i<rv, i++)
         {
            buf[i]=toupper(buf[i]);
         }
         rv=write(clifd, buf, rv);
         if(rv < 0)
         {
            printf("failture:%s\n", strerror(errno));
            close(clifd);
            pthread_exit(NULL);
         }
     }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值