UNP第二十六章 线程

概述

当一个进程需要另一个实体来完成某事时,它就fork一个子进程并让子进程去执行处理。之前的多进程服务器:父进程accept一个连接,fork一个子进程,该子进程处理与该连接对端的客户之间的通信。但是fork调用却存在以下两个问题:

  • fork是昂贵的。fork要把父进程的内存映像复制到子进程,并在子进程中复制所有描述符。当今的实现使用称为写时复制的技术,用以避免在子进程切实需要自己的副本之前把父进程的数据空间复制到子进程。
  • fork返回之后父子进程之间信息的传递需要进程间通信IPC机制。

而线程能够有助于解决上述两个问题。

基本线程函数:创建和终止

pthread_create函数–类似fork

#include <pthread.h>
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);
//成功返回0,出错则为正的EXXX值
  • 一个进程内的每个线程都由一个线程ID标志,其数据类型为pthread_t,新的进程创建成功,其tid以指针的形式返回。
  • 每个线程都有许多属性,可以通过pthread_attr_t变量 *attr指定一个线程的属性;若置attr为空指针,则线程属性采取默认。
  • 创建线程时我们最后指定的参数由该线程执行的函数及其参数。该函数的地址由func参数指定,该函数的唯一调用参数的指针arg。

pthread_join函数–类似waitpid

我们可以调用一个pthread_join等待一个给定线程终止。

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

其中,我们必须指定要等待线程tid。

pthread_self函数

每个线程使用pthread_self获取自身的线程ID

pthread_t pthread_self(void);

pthread_detach函数

一个线程或者是可汇合的,或者是脱离的。当一个可汇合的线程终止时,它的线程ID和退出状态将留存到另一个对它调用pthread_join的线程;脱离的线程却像守护进程,当它们终止时,相关资源全都被释放。
pthread_detach函数把指定的线程转变为脱离状态。

int pthread_detach(pthread_t tid);
//自身脱离线程调用
pthread_detach(pthread_self());

pthread_exit函数

终止一个线程

void pthread_exit(void *status);

使用线程的str_cli函数

void    *copyto(void *);
static int  sockfd;     
static FILE *fp;  //所有线程共享的全局变量

void
str_cli(FILE *fp_arg, int sockfd_arg)
{
    char        recvline[MAXLINE];
    pthread_t   tid;

    sockfd = sockfd_arg;    /* copy arguments to externals */
    fp = fp_arg;

    Pthread_create(&tid, NULL, copyto, NULL);  //即是把从标准输入读入放入线程中

    while (Readline(sockfd, recvline, MAXLINE) > 0)
        Fputs(recvline, stdout);
}
void *
copyto(void *arg)
{
    char    sendline[MAXLINE];

    while (Fgets(sendline, MAXLINE, fp) != NULL)
        Writen(sockfd, sendline, strlen(sendline));

    Shutdown(sockfd, SHUT_WR);  /* EOF on stdin, send FIN */

    return(NULL);
    /* 4return (i.e., thread terminates) when EOF on stdin */
}

这里可以总结一下如何在程序中加入线程:

  • 声明线程要执行的函数 void copyto(void ),之后在pthread_create中作为函数参数进行传递;
  • 创建所有线程能够共享的全局变量 static int sockfd; static FILE *fp;
  • Pthread_create(&tid, NULL, copyto, NULL),这里设置线程ID,线程属性为默认属性,copyto函数为无参模式;
  • 在copyto函数中实现从标准输入读入数据并写入TCP套接字,如果输入EOF(也就是ctrl+d),则调用Shutdown函数置TCP为半关闭状态。

给线程传递函数

将整数变量connfd类型强制转换成void指针并不能保证在所有系统上都能起作用,例如下面这种简单地把connfd的地址传递给新线程:

connfd = Accept(listendfd, cliaddr, &len);
Pthread_create(&tid, NULL, &doit, &connfd);
...
...
static void *
doit(void *arg)
{
    int connfd; //只有一个变量
    connfd = *((int *)arg);
    Pthread_detach(pthread_self());
    str_echo(connfd);
    Close(connfd);
    return NULL;
}

主线程中只有一个变量,通过参数main函数传地址从arg获取。每次调用Accept函数该变量都会被复写以一个新值,因此可能发生下述情况:

  • accept返回,主线程把返回值(例如新的描述符为5)存入connfd后调用pthread_create。pthread_create的最后一个参数是指向connfd的指针而不是connfd的内容。
  • Pthread函数库创建一个线程,并准备调用doit函数启动执行。
  • 另一个连接就绪且主线程在新创建的线程开始之前再次运行。accept返回,主线程把返回值(例如新的描述符为6)存入connfd后调用pthread_create。
    尽管主线程共创建了两个线程,但是它们操作的都是存放在connfd中的最终值(假设是6)。问题出在多个线程不是同步地访问一个共享变量,可以通过传递connfd的值来解决上述问题。
int *iptr;
iptr = Malloc(sizeof(int));
*iptr = Accept(...);
Pthread_create(&tid,NULL, &doit, iptr);

解析:: 每当调用accept时,我们先调用一个malloc为每个线程分配一个整数变量的内存空间(用于存放已连接描述符),这使得每个线程都有各自的已连接描述符副本。

malloc、free、printf均是不可重入函数,不能与中断函数和信号处理函数同时调用,但它们却是线程安全函数,可以被多个线程调用。

线程特定数据

线程特定数据又称线程私有数据。当同一进程内不同线程同时调用使用静态变量的函数时,可能使得静态变量无法为不同的线程保存各自的值,因此引入线程特定数据。
这里写图片描述
该元素的索引(0-127)称为键(key),返回给调用线程的正是这个索引。系统还在进程内维护关于每个线程的多条信息,这些特定于线程的信息我们称之为Pthread结构,其内容是pkey数组的一个128个元素的指针数组。
这里写图片描述
pkey数组的所有元素都被初始化为空指针。这128个指针是和进程内的128个可能的“键”逐一关联的值。这样作为键值对,键为索引0~127,值则为指针,指针指向真正的线程特定数据。
在pthread_key_create函数中有一个参数为void (*destructor)(void *value),这是一个指向折构函数的指针。当一个线程终止时,系统将扫描该线程的pkey数组,为每个非空的pkey(即已分配内存的线程索引)指针调用对应的折构函数,释放其线程特定的数据。
线程特定数据的获取和存放:

#include "pthread.h"
void *pthread_getspecifi(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);

互斥锁

互斥锁适合于防止同时访问某个共享变量。

pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
Pthread_mutex_lock(&counter_mutex); //锁住互斥变量
 //对counter进行操作
Pthread_mutex_unlock(&counter_mutex);//解锁

条件变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值