线程有时称为轻权进程(lightweight process)
同一进程内的所有线程共享相同的全局内存。这使得线程之间易于共享信息,然后这样也会带来同步的问题
同一进程内的所有线程处理共享全局变量外还共享:
1.进程指令
2.大多数数据
3.打开的文件(即描述符)
4.信号处理函数和信号处置
5.当前工作目录
6.用户ID和组ID
不过每个线程有各自的:
1.线程ID
2.寄存器集合,包括程序计数器和栈指针
3.栈(用于存放局部变量和返回地址)
4.errno
5.信号掩码
6.优先级
基本线程函数
有关线程的一些用法跟概念可以查看之前apue的笔记 http://www.cnblogs.com/runnyu/p/4643363.html
下面只列出这些函数的原型
#include <pthread.h> int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr,void *(*start_rtn)(void *),void *restrict arg); int pthread_join(pthread_t thread,void **rval_ptr); pthread_t pthread_self(void); int pthread_detach(pthread_t tid); void pthread_exit(void *rval_ptr);
使用线程的str_cli函数
我们使用线程把第五章中使用fork的str_cli函数重新编写成改用线程
1 #include "unpthread.h" 2 3 void *copyto(void *); 4 5 static int sockfd; /* global for both threads to access */ 6 static FILE *fp; 7 8 void 9 str_cli(FILE *fp_arg, int sockfd_arg) 10 { 11 char recvline[MAXLINE]; 12 pthread_t tid; 13 14 sockfd = sockfd_arg; /* copy arguments to externals */ 15 fp = fp_arg; 16 17 Pthread_create(&tid, NULL, copyto, NULL); 18 19 while (Readline(sockfd, recvline, MAXLINE) > 0) 20 Fputs(recvline, stdout); 21 } 22 23 void * 24 copyto(void *arg) 25 { 26 char sendline[MAXLINE]; 27 28 while (Fgets(sendline, MAXLINE, fp) != NULL) 29 Writen(sockfd, sendline, strlen(sendline)); 30 31 Shutdown(sockfd, SHUT_WR); /* EOF on stdin, send FIN */ 32 33 return(NULL); 34 /* 4return (i.e., thread terminates) when EOF on stdin */ 35 }
使用线程的TCP回射客户端程序
我们重新编写第五章的TCP回射服务器程序,该成为每个客户使用一个线程,而不是为每个客户使用一个子进程。
1 #include "unpthread.h" 2 3 static void *doit(void *); /* each thread executes this function */ 4 5 int 6 main(int argc, char **argv) 7 { 8 int listenfd, connfd; 9 pthread_t tid; 10 socklen_t addrlen, len; 11 struct sockaddr *cliaddr; 12 13 if (argc == 2) 14 listenfd = Tcp_listen(NULL, argv[1], &addrlen); 15 else if (argc == 3) 16 listenfd = Tcp_listen(argv[1], argv[2], &addrlen); 17 else 18 err_quit("usage: tcpserv01 [ <host> ] <service or port>"); 19 20 cliaddr = Malloc(addrlen); 21 22 for ( ; ; ) { 23 len = addrlen; 24 connfd = Accept(listenfd, cliaddr, &len); 25 Pthread_create(&tid, NULL, &doit, (void *) connfd); 26 } 27 } 28 29 static void * 30 doit(void *arg) 31 { 32 Pthread_detach(pthread_self()); 33 str_echo((int) arg); /* same function as before */ 34 Close((int) arg); /* done with connected socket */ 35 return(NULL); 36 }
pthread_create的第四个参数是传入线程函数的参数(void *类型),在线程函数中可以把该参数强转成需要的类型
这个程序由一个问题是:两个线程使用的参数都是同一个共享变量connfd,会产生同步的问题。因此我们需要的只是把connfd的值(而不是指向该变量的指针)传递给pthread_create。
下面程序是一种解决本问题的办法
1 #include "unpthread.h" 2 3 static void *doit(void *); /* each thread executes this function */ 4 5 int 6 main(int argc, char **argv) 7 { 8 int listenfd, *iptr; 9 thread_t tid; 10 socklen_t addrlen, len; 11 struct sockaddr *cliaddr; 12 13 if (argc == 2) 14 listenfd = Tcp_listen(NULL, argv[1], &addrlen); 15 else if (argc == 3) 16 listenfd = Tcp_listen(argv[1], argv[2], &addrlen); 17 else 18 err_quit("usage: tcpserv01 [ <host> ] <service or port>"); 19 20 cliaddr = Malloc(addrlen); 21 22 for ( ; ; ) { 23 len = addrlen; 24 iptr = Malloc(sizeof(int)); 25 *iptr = Accept(listenfd, cliaddr, &len); 26 Pthread_create(&tid, NULL, &doit, iptr); 27 } 28 } 29 30 static void * 31 doit(void *arg) 32 { 33 int connfd; 34 35 connfd = *((int *) arg); 36 free(arg); 37 38 Pthread_detach(pthread_self()); 39 str_echo(connfd); /* same function as before */ 40 Close(connfd); /* done with connected socket */ 41 return(NULL); 42 }
每当调用accept时,我们先分配一个整数变量的内存空间,用于存放accept返回的已连接的描述符。使得每个线程都有之间的已连接描述符副本。
线程获取已连接的描述符的值之后调用free释放内存空间。但是这两个函数是不可重入的,从某个信号处理函数中调用这两个函数之一可能会导致灾难性的后果。
线程安全函数
除了下图所示的函数外,POSIX.1要求由POSIX.1和ANSI C标准定义的所有函数都是线程安全的
线程特定数据
可以参考apue第十二章的学习笔记 http://www.cnblogs.com/runnyu/p/4643764.html
下面给出需要的函数原型
#include <pthread.h> int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *)); int pthread_one(pthread_one_t *onceptr,void (*init)(void)); void *pthread_getspecific(pthread_key_t key); int pthread_setspecific(pthread_key_t key,const void *value);
系统为每个进程维护一个我们称之为key结构的结构数组
key结构中的标志指示这个数组元素是否正在使用。当一个线程调用pthread_key_create创建一个新的线程特定数据元素时,系统搜索其key结构数组找出第一个不在使用的元素。
该元素的索引(0-127)称为key,返回给调用线程的正是这个索引。
除了进程范围的key结构数组外,系统还在进程内维护关于每个线程的多条信息(Pthread结构)。
当我们调用pthread_key_create创建一个键时,系统告诉我们这个键(索引)。每个线程可以随后为该键存储一个值(指针),而这个指针通常又是每个线程通过调用malloc获得的。
下面我们修改原来的readline函数,它是遵循如下步骤的代码:
1.一个进程被启动,多个线程被创建
2.其中一个线程是首个调用readline函数的线程(0),该函数转而调用pthread_key_create(系统返回key结构中第一个未用元素的索引给调用者,假设是1)。我们使用pthread_once函数确保pthread_key_create只是被第一个调用readline的线程所调用
3.readline调用pthread_getspecific获取本线程的pkey[1]值,返回值是一个空指针。readline于是调用malloc分配内存区,并调用pthread_setspecific把相应所创建键的线程特定数据指针设置为指向刚刚分配的内存区。
4.另一个线程调用readline,当时也许线程0仍然在readline内执行。
readline调用pthread_once试图初始化它的线程特定数据元素所用的键,不过既然初始化函数已被调用过,它就不再被调用。
5.readline调用pthread_getspecific获取本线程的pkey[1]值,返回值是一个空指针。于是它也调用malloc,再调用pthread_setspecific
6.线程n继续在readline中执行,使用和修改它自己的线程特定数据
当一个线程终止时,系统将扫描该线程的pkey数组,为每个非空的pkey指针调用相应的析构函数。
下面是readline函数的代码
1 /* include readline1 */ 2 #include "unpthread.h" 3 4 static pthread_key_t rl_key; 5 static pthread_once_t rl_once = PTHREAD_ONCE_INIT; 6 7 static void 8 readline_destructor(void *ptr) 9 { 10 free(ptr); 11 } 12 13 static void 14 readline_once(void) 15 { 16 Pthread_key_create(&rl_key, readline_destructor); 17 } 18 19 typedef struct { 20 int rl_cnt; /* initialize to 0 */ 21 char *rl_bufptr; /* initialize to rl_buf */ 22 char rl_buf[MAXLINE]; 23 } Rline; 24 /* end readline1 */ 25 26 /* include readline2 */ 27 static ssize_t 28 my_read(Rline *tsd, int fd, char *ptr) 29 { 30 if (tsd->rl_cnt <= 0) { 31 again: 32 if ( (tsd->rl_cnt = read(fd, tsd->rl_buf, MAXLINE)) < 0) { 33 if (errno == EINTR) 34 goto again; 35 return(-1); 36 } else if (tsd->rl_cnt == 0) 37 return(0); 38 tsd->rl_bufptr = tsd->rl_buf; 39 } 40 41 tsd->rl_cnt--; 42 *ptr = *tsd->rl_bufptr++; 43 return(1); 44 } 45 46 ssize_t 47 readline(int fd, void *vptr, size_t maxlen) 48 { 49 size_t n, rc; 50 char c, *ptr; 51 Rline *tsd; 52 53 Pthread_once(&rl_once, readline_once); 54 if ( (tsd = pthread_getspecific(rl_key)) == NULL) { 55 tsd = Calloc(1, sizeof(Rline)); /* init to 0 */ 56 Pthread_setspecific(rl_key, tsd); 57 } 58 59 ptr = vptr; 60 for (n = 1; n < maxlen; n++) { 61 if ( (rc = my_read(tsd, fd, &c)) == 1) { 62 *ptr++ = c; 63 if (c == ' ') 64 break; 65 } else if (rc == 0) { 66 *ptr = 0; 67 return(n - 1); /* EOF, n - 1 bytes read */ 68 } else 69 return(-1); /* error, errno set by read() */ 70 } 71 72 *ptr = 0; 73 return(n); 74 } 75 /* end readline2 */ 76 77 ssize_t 78 Readline(int fd, void *ptr, size_t maxlen) 79 { 80 ssize_t n; 81 82 if ( (n = readline(fd, ptr, maxlen)) < 0) 83 err_sys("readline error"); 84 return(n); 85 }
Web客户与同时连接
我们修改第十六章的Web客户程序例子,把它编写成用线程代替非阻塞connect(为每个连接创建一个线程)。
1.全局变量和main函数
1 #include "unpthread.h" 2 #include <thread.h> /* Solaris threads */ 3 4 #define MAXFILES 20 5 #define SERV "80" /* port number or service name */ 6 7 struct file { 8 char *f_name; /* filename */ 9 char *f_host; /* hostname or IP address */ 10 int f_fd; /* descriptor */ 11 int f_flags; /* F_xxx below */ 12 pthread_t f_tid; /* thread ID */ 13 } file[MAXFILES]; 14 #define F_CONNECTING 1 /* connect() in progress */ 15 #define F_READING 2 /* connect() complete; now reading */ 16 #define F_DONE 4 /* all done */ 17 18 #define GET_CMD "GET %s HTTP/1.0 " 19 20 int nconn, nfiles, nlefttoconn, nlefttoread; 21 22 void *do_get_read(void *); 23 void home_page(const char *, const char *); 24 void write_get_cmd(struct file *); 25 26 int 27 main(int argc, char **argv) 28 { 29 int i, n, maxnconn; 30 pthread_t tid; 31 struct file *fptr; 32 33 if (argc < 5) 34 err_quit("usage: web <#conns> <IPaddr> <homepage> file1 ..."); 35 maxnconn = atoi(argv[1]); 36 37 nfiles = min(argc - 4, MAXFILES); 38 for (i = 0; i < nfiles; i++) { 39 file[i].f_name = argv[i + 4]; 40 file[i].f_host = argv[2]; 41 file[i].f_flags = 0; 42 } 43 printf("nfiles = %d ", nfiles); 44 45 home_page(argv[2], argv[3]); 46 47 nlefttoread = nlefttoconn = nfiles; 48 nconn = 0; 49 /* end web1 */ 50 /* include web2 */ 51 while (nlefttoread > 0) { 52 while (nconn < maxnconn && nlefttoconn > 0) { 53 /* 4find a file to read */ 54 for (i = 0 ; i < nfiles; i++) 55 if (file[i].f_flags == 0) 56 break; 57 if (i == nfiles) 58 err_quit("nlefttoconn = %d but nothing found", nlefttoconn); 59 60 file[i].f_flags = F_CONNECTING; 61 Pthread_create(&tid, NULL, &do_get_read, &file[i]); 62 file[i].f_tid = tid; 63 nconn++; 64 nlefttoconn--; 65 } 66 67 if ( (n = thr_join(0, &tid, (void **) &fptr)) != 0) 68 errno = n, err_sys("thr_join error"); 69 70 nconn--; 71 nlefttoread--; 72 printf("thread id %d for %s done ", tid, fptr->f_name); 73 } 74 75 exit(0); 76 }
2.每个新线程执行函数do_get_read。该函数建立TCP连接,给服务器发送一个HTTP GET命令,并读入来自服务器的应答
1 void * 2 do_get_read(void *vptr) 3 { 4 int fd, n; 5 char line[MAXLINE]; 6 struct file *fptr; 7 8 fptr = (struct file *) vptr; 9 10 fd = Tcp_connect(fptr->f_host, SERV); 11 fptr->f_fd = fd; 12 printf("do_get_read for %s, fd %d, thread %d ", 13 fptr->f_name, fd, fptr->f_tid); 14 15 write_get_cmd(fptr); /* write() the GET command */ 16 17 /* 4Read server's reply */ 18 for ( ; ; ) { 19 if ( (n = Read(fd, line, MAXLINE)) == 0) 20 break; /* server closed connection */ 21 22 printf("read %d bytes from %s ", n, fptr->f_name); 23 } 24 printf("end-of-file on %s ", fptr->f_name); 25 Close(fd); 26 fptr->f_flags = F_DONE; /* clears F_READING */ 27 28 return(fptr); /* terminate thread */ 29 }
线程同步问题
可以查看apue的学习笔记 http://www.cnblogs.com/runnyu/p/4643363.html