线程有时称为轻权进程(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函数重新编写成改用线程

 #include    "unpthread.h"

 void    *copyto(void *);

 static int    sockfd;        /* global for both threads to access */
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) > )
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 */
}

使用线程的TCP回射客户端程序

我们重新编写第五章的TCP回射服务器程序,该成为每个客户使用一个线程,而不是为每个客户使用一个子进程。

 #include    "unpthread.h"

 static void    *doit(void *);        /* each thread executes this function */

 int
main(int argc, char **argv)
{
int listenfd, connfd;
pthread_t tid;
socklen_t addrlen, len;
struct sockaddr *cliaddr; if (argc == )
listenfd = Tcp_listen(NULL, argv[], &addrlen);
else if (argc == )
listenfd = Tcp_listen(argv[], argv[], &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); /* same function as before */
Close((int) arg); /* done with connected socket */
return(NULL);
}

pthread_create的第四个参数是传入线程函数的参数(void *类型),在线程函数中可以把该参数强转成需要的类型

这个程序由一个问题是:两个线程使用的参数都是同一个共享变量connfd,会产生同步的问题。因此我们需要的只是把connfd的值(而不是指向该变量的指针)传递给pthread_create。

下面程序是一种解决本问题的办法

 #include    "unpthread.h"

 static void    *doit(void *);        /* each thread executes this function */

 int
main(int argc, char **argv)
{
int listenfd, *iptr;
thread_t tid;
socklen_t addrlen, len;
struct sockaddr *cliaddr; if (argc == )
listenfd = Tcp_listen(NULL, argv[], &addrlen);
else if (argc == )
listenfd = Tcp_listen(argv[], argv[], &addrlen);
else
err_quit("usage: tcpserv01 [ <host> ] <service or port>"); cliaddr = Malloc(addrlen); for ( ; ; ) {
len = addrlen;
iptr = Malloc(sizeof(int));
*iptr = Accept(listenfd, cliaddr, &len);
Pthread_create(&tid, NULL, &doit, iptr);
}
} static void *
doit(void *arg)
{
int connfd; connfd = *((int *) arg);
free(arg); Pthread_detach(pthread_self());
str_echo(connfd); /* same function as before */
Close(connfd); /* done with connected socket */
return(NULL);
}

每当调用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函数的代码

 /* include readline1 */
#include "unpthread.h" static pthread_key_t rl_key;
static pthread_once_t rl_once = PTHREAD_ONCE_INIT; static void
readline_destructor(void *ptr)
{
free(ptr);
} static void
readline_once(void)
{
Pthread_key_create(&rl_key, readline_destructor);
} typedef struct {
int rl_cnt; /* initialize to 0 */
char *rl_bufptr; /* initialize to rl_buf */
char rl_buf[MAXLINE];
} Rline;
/* end readline1 */ /* include readline2 */
static ssize_t
my_read(Rline *tsd, int fd, char *ptr)
{
if (tsd->rl_cnt <= ) {
again:
if ( (tsd->rl_cnt = read(fd, tsd->rl_buf, MAXLINE)) < ) {
if (errno == EINTR)
goto again;
return(-);
} else if (tsd->rl_cnt == )
return();
tsd->rl_bufptr = tsd->rl_buf;
} tsd->rl_cnt--;
*ptr = *tsd->rl_bufptr++;
return();
} ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
size_t n, rc;
char c, *ptr;
Rline *tsd; Pthread_once(&rl_once, readline_once);
if ( (tsd = pthread_getspecific(rl_key)) == NULL) {
tsd = Calloc(, sizeof(Rline)); /* init to 0 */
Pthread_setspecific(rl_key, tsd);
} ptr = vptr;
for (n = ; n < maxlen; n++) {
if ( (rc = my_read(tsd, fd, &c)) == ) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == ) {
*ptr = ;
return(n - ); /* EOF, n - 1 bytes read */
} else
return(-); /* error, errno set by read() */
} *ptr = ;
return(n);
}
/* end readline2 */ ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
ssize_t n; if ( (n = readline(fd, ptr, maxlen)) < )
err_sys("readline error");
return(n);
}

Web客户与同时连接

我们修改第十六章的Web客户程序例子,把它编写成用线程代替非阻塞connect(为每个连接创建一个线程)。

1.全局变量和main函数

 #include    "unpthread.h"
#include <thread.h> /* Solaris threads */ #define MAXFILES 20
#define SERV "80" /* port number or service name */ struct file {
char *f_name; /* filename */
char *f_host; /* hostname or IP address */
int f_fd; /* descriptor */
int f_flags; /* F_xxx below */
pthread_t f_tid; /* thread ID */
} file[MAXFILES];
#define F_CONNECTING 1 /* connect() in progress */
#define F_READING 2 /* connect() complete; now reading */
#define F_DONE 4 /* all done */ #define GET_CMD "GET %s HTTP/1.0\r\n\r\n" int nconn, nfiles, nlefttoconn, nlefttoread; void *do_get_read(void *);
void home_page(const char *, const char *);
void write_get_cmd(struct file *); int
main(int argc, char **argv)
{
int i, n, maxnconn;
pthread_t tid;
struct file *fptr; if (argc < )
err_quit("usage: web <#conns> <IPaddr> <homepage> file1 ...");
maxnconn = atoi(argv[]); nfiles = min(argc - , MAXFILES);
for (i = ; i < nfiles; i++) {
file[i].f_name = argv[i + ];
file[i].f_host = argv[];
file[i].f_flags = ;
}
printf("nfiles = %d\n", nfiles); home_page(argv[], argv[]); nlefttoread = nlefttoconn = nfiles;
nconn = ;
/* end web1 */
/* include web2 */
while (nlefttoread > ) {
while (nconn < maxnconn && nlefttoconn > ) {
/* 4find a file to read */
for (i = ; i < nfiles; i++)
if (file[i].f_flags == )
break;
if (i == nfiles)
err_quit("nlefttoconn = %d but nothing found", nlefttoconn); file[i].f_flags = F_CONNECTING;
Pthread_create(&tid, NULL, &do_get_read, &file[i]);
file[i].f_tid = tid;
nconn++;
nlefttoconn--;
} if ( (n = thr_join(, &tid, (void **) &fptr)) != )
errno = n, err_sys("thr_join error"); nconn--;
nlefttoread--;
printf("thread id %d for %s done\n", tid, fptr->f_name);
} exit();
}

2.每个新线程执行函数do_get_read。该函数建立TCP连接,给服务器发送一个HTTP GET命令,并读入来自服务器的应答

 void *
do_get_read(void *vptr)
{
int fd, n;
char line[MAXLINE];
struct file *fptr; fptr = (struct file *) vptr; fd = Tcp_connect(fptr->f_host, SERV);
fptr->f_fd = fd;
printf("do_get_read for %s, fd %d, thread %d\n",
fptr->f_name, fd, fptr->f_tid); write_get_cmd(fptr); /* write() the GET command */ /* 4Read server's reply */
for ( ; ; ) {
if ( (n = Read(fd, line, MAXLINE)) == )
break; /* server closed connection */ printf("read %d bytes from %s\n", n, fptr->f_name);
}
printf("end-of-file on %s\n", fptr->f_name);
Close(fd);
fptr->f_flags = F_DONE; /* clears F_READING */ return(fptr); /* terminate thread */
}

线程同步问题

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

UNP学习笔记(第二十六章 线程)的更多相关文章

  1. UNP学习笔记(第六章 I/O复用)

    I/O模型 首先我们将查看UNIX下可用的5种I/O模型的基本区别: 1.阻塞式I/O 2.非阻塞式I/O 3.I/O复用(select和poll) 4.信号驱动式I/O(SIGIO) 5.异步I/O ...

  2. 【WPF学习】第二十六章 Application类——应用程序的生命周期

    在WPF中,应用程序会经历简单的生命周期.在应用程序启动后,将立即创建应用程序对象,在应用程序运行时触发各种应用程序事件,你可以选择监视其中的某些事件.最后,当释放应用程序对象时,应用程序将结束. 一 ...

  3. Python学习笔记第二十六周(Django补充)

    一.基于jQuery的ajax实现(最底层方法:$.jax()) $.ajax( url: type:''POST“ ) $.get(url,[data],[callback],[type])  #c ...

  4. [EXTJS5学习笔记]第二十六节 在eclipse/myeclipse中使用sencha extjs的插件

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/40507383 插件下载: http://download.csdn.net/detai ...

  5. WP8.1学习系列(第二十六章)——控件模板

    在本文中 自定义控件模板示例 指定控件的可视结构. 指定控件的可视行为 使用工具轻松处理主题 控件和辅助功能 了解有关控件默认模板的详细信息 控件模板中的主题资源 相关主题 在 XAML 框架中,如果 ...

  6. “全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. 《HTTP 权威指南》笔记:第十六章&第十七章 国际化、内容协商与转码

    <HTTP 权威指南>笔记:第十六章 国际化 客户端通过在请求报文中的 Accept-Language 首部和 Accept-Charset 首部来告知服务器:“我理解这些语言.”服务器通 ...

  8. “全栈2019”Java第二十六章:流程控制语句中循环语句do-while

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第六章:在Direct3D中绘制

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第六章:在Direct3D中绘制 代码工程地址: https://gi ...

随机推荐

  1. 声卡(Sound Card)基本概念

    声卡 (Sound Card)是实现声音的模拟/数字信号相互转换.信号处理的一种硬件. 声卡的基本功能是把来自话筒.磁带.光盘的原始声音信号加以转换(模数转换或者数模转换),输出到耳机.扬声器.扩音机 ...

  2. BootStrap导入及其使用

    BootStrap主要是一个CSS框架,用于页面布局 <!DOCTYPE html> <html lang="en"> <head> <m ...

  3. 山贼集团 (group)

    山贼集团 (group) 题目描述 某山贼集团在绿荫村拥有强大的势力,整个绿荫村由N个连通的小村落组成,并且保证对于每两个小村落有且仅有一条简单路径相连.小村落用阿拉伯数字编号为1,2,3,4,-,n ...

  4. bzoj3609【HEOI2014】人人尽说江南好

    题意:http://www.lydsy.com/JudgeOnline/problem.php?id=3609 sol :博弈论  通过打表找规律,发现答案是%m循环的,且当m为偶数时取反  因为我太 ...

  5. g2o安装

    1.安装依赖项 sudo apt-get install libeigen3-dev libsuitesparse-dev libqt4-dev qt4-qmake 2.安装依赖项  libqglvi ...

  6. python(7)-- 文件I/O

    1 打印到屏幕:print 语句.你可以给它传递零个或多个用逗号隔开的表达式.此函数把你传递的表达式转换成一个字符串表达式,并将结果写到标准输出,eg:print "Python 是一个非常 ...

  7. Maven多模块项目依赖管理

    Maven多模块项目依赖管理及dependencies与dependencyManagement的区别 转自:http://blog.csdn.net/liutengteng130/article/d ...

  8. EnableViewState 属性

    原文发布时间为:2009-10-25 -- 来源于本人的百度文章 [由搬家工具导入] 指示是否在页请求之间保持视图状态。如果要保持视图状态,则为 true;否则为 false。默认值为 true。 自 ...

  9. PHP json_encode 转换成空对象和空数组

    对于以下对象 $foo = array( "bar1" => array(), "bar2" => array() ); 我想转换成 { " ...

  10. hdu 5020(斜率的表示+STL)

    Revenge of Collinearity Time Limit: 8000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/ ...