UNP学习笔记(第二十六章 线程)

线程有时称为轻权进程(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 }
View Code

 

 

 

使用线程的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 }
View Code

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 }
View Code

每当调用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 == '\n')
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 }
View Code

 

 

 

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\r\n\r\n"
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\n", 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\n", tid, fptr->f_name);
73     }
74 
75     exit(0);
76 }
View Code

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\n",
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", n, fptr->f_name);
23     }
24     printf("end-of-file on %s\n", fptr->f_name);
25     Close(fd);
26     fptr->f_flags = F_DONE;        /* clears F_READING */
27 
28     return(fptr);        /* terminate thread */
29 }
View Code

 

 

 

线程同步问题

可以查看apue的学习笔记 http://www.cnblogs.com/runnyu/p/4643363.html

转载于:https://www.cnblogs.com/runnyu/p/4670414.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值