当一个进程需要另一个实体来完成某事时,它就fork一个子进程并让子进程去执行处理。之前的多进程服务器:父进程accept一个连接,fork一个子进程,该子进程处理与该连接对端的客户之间的通信。但是fork调用却存在以下两个问题:
而线程能够有助于解决上述两个问题。
#include
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);
//成功返回0,出错则为正的EXXX值
我们可以调用一个pthread_join等待一个给定线程终止。
#include
int pthread_join(pthread_t *tid, void **status);
其中,我们必须指定要等待线程tid。
每个线程使用pthread_self获取自身的线程ID
pthread_t pthread_self(void);
一个线程或者是可汇合的,或者是脱离的。当一个可汇合的线程终止时,它的线程ID和退出状态将留存到另一个对它调用pthread_join的线程;脱离的线程却像守护进程,当它们终止时,相关资源全都被释放。
pthread_detach函数把指定的线程转变为脱离状态。
int pthread_detach(pthread_t tid);
//自身脱离线程调用
pthread_detach(pthread_self());
终止一个线程
void pthread_exit(void *status);
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 */
}
这里可以总结一下如何在程序中加入线程:
将整数变量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函数该变量都会被复写以一个新值,因此可能发生下述情况:
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);//解锁