概述
传统unix中,如果一个进程需要另一个实体完成某事,就会fork一个子进程然后让其执行。
但是fork调用存在着以下几个问题:
-
fork是昂贵的。fork要将父进程的内存映像复制到子进程,并在子进程复制所有描述符。即便现在使用了“写时复制”技术,fork依然是昂贵的
-
fork返回之后,父子进程之间的通信需要进程间通信(IPC)。调用fork之前父进程向子进程传递信息是容易的,但是子进程向父进程返回信息确很困难
线程有助于解决这些问题,因为
- 线程创建成本较为低廉,比进程的创建可能快10-100倍
- 线程共享相同的全局内存,使得线程之间易于共享信息
基本线程函数:创建和终止
pthread_create函数
当程序由exec启动执行时候,称为初始线程或者主线程的单个线程就创建了。其他都用pthread_create函数创建。
int pthread_create(pthread_t *tid,
const pthread_attr_t * attr,
void *(*func)(void * ),
void * arg);
当线程创建成功的时候tid返回线程ID(pthraed_t);我们可以通过attr设置诸如线程优先级、初始栈大小、是否是守护线程等,一般可以用NULL表示使用默认值;func表示要该线程执行的函数,线程会调用该函数,然后通过显式的调用pthread_exit或者隐式的终止(通过return),函数的调用参数由arg指定,如果要传入多个数值,需要封装成struct然后再传入。
线程如果创建成功,函数返回0,否则返回正值表示具体错误(而不是返回-1并且设置errno)
pthread_join函数
我们可以用join来等待一个给定线程的终止,类似于waitpid。为此我们需要制定等待线程的tid(而不能等待任意一个线程结束)
int pthread_join(pthread_t *tid, void ** status)
如果status非空,那么所等待线程的返回值就会存入status指定的位置
pthread_self函数
线程可以调用该函数获知自己的tid(类似于进程中的getpid)
pthread_t pthread_self(void);
pthread_detach函数
一个线程要么是可汇合的(joinable,默认值),要么是脱离的(detached),一个可汇合的线程结束时候,线程ID和退出状态会留存到另一个线程对它调用pthread_join。脱离的线程终止的时候,所有相关的资源都被释放,不能等待终止。
int pthread_detach(pthread_t);
本函数一般是由想让自己脱离的线程调用,如以下语句
pthread_detach(pthread_self());
pthread_exit函数
让一个线程终止的方法之一就是调用pthread_exit函数
void pthread_exit(void * status);
如果该线程未曾脱离,其线程ID和退出状态会一直留存到调用进程内的某个其他线程对它调用pthread_join。
status不能指向局部于调用线程的对象,因为线程终止时候对象也消失。
线程终止的其他方法有:
- 启动线程的函数返回。其返回值就是线程的终止状态
- main返回或者任何线程调用了exit,进程终止,所有线程也随之终止。
使用线程的str_cli函数
#include "../unp.h"
#include <pthread.h>
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;
fp = fp_arg;
pthread_create(&tid, NULL, copyto, NULL);
}
void *copyto(void *arg)
{
char sendline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL)
Writen(sockfd, sendline, strlen(sendline));
Shutdown(sockfd, SHUT_WR);
return (NULL);
}
使用线程的TCP回射服务器程序
#include "../unp.h"
#include <pthread.h>
static void *doit(void *);
int main(int argc, char **argv)
{
int listenfd, connfd;
pthread_t tid;
socklen_t addrlen, len;
struct sockaddr *cliaddr;
if (argc == 2)
listenfd = Tcp_listen(NULL, argv[1], &addrlen);
else if (argc == 3)
listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
else
err_quit("usage: tcpserv01 [ <host> ] <service or port>");
cliaddr = Malloc(addrlen);
for (;;)
{
len = addrlen;
connfd = Accept(listenfd, cliaddr, &len);
pthread_create(&tid, NULL, &doit, (void *)connfd);//这里直接就是一个连接一个线程
}
}
static void *doit(void *arg)
{
pthread_detach(pthread_self());
str_echo((int)arg);
Close((int)arg);
return NULL;
}
注意在上述调用pthread_create中我们传入了整数类型connfd,ANSI C并不能保证这样做有用,只有在指针大小和整数类型大小一致的系统上才能起作用,不过绝大多数UNIX都符合这个特征。
那么问题来了?为什么我们不直接传入connfd地址呢?答案是不同的线程因此会共享该地址,从而互相干扰。
除此之外我们可以提供一个更好的方法
#include "../unp.h"
#include <pthread.h>
static void *doit(void *);
int main(int argc, char **argv)
{
int listenfd, *iptr;
pthread_t tid;
socklen_t addrlen, len;
struct sockaddr *cliaddr;
if (argc == 2)
listenfd = Tcp_listen(NULL, argv[1], &addrlen);