📄 99.htm
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>CTerm非常精华下载</title>
</head>
<body bgcolor="#FFFFFF">
<table border="0" width="100%" cellspacing="0" cellpadding="0" height="577">
<tr><td width="32%" rowspan="3" height="123"><img src="DDl_back.jpg" width="300" height="129" alt="DDl_back.jpg"></td><td width="30%" background="DDl_back2.jpg" height="35"><p align="center"><a href="http://apue.dhs.org"><font face="黑体"><big><big>123</big></big></font></a></td></tr>
<tr>
<td width="68%" background="DDl_back2.jpg" height="44"><big><big><font face="黑体"><p align="center"> ● UNIX网络编程 (BM: clown) </font></big></big></td></tr>
<tr>
<td width="68%" height="44" bgcolor="#000000"><font face="黑体"><big><big><p align="center"></big></big><a href="http://cterm.163.net"><img src="banner.gif" width="400" height="60" alt="banner.gif"border="0"></a></font></td>
</tr>
<tr><td width="100%" colspan="2" height="100" align="center" valign="top"><br><p align="center">[<a href="index.htm">回到开始</a>][<a href="54.htm">上一层</a>][<a href="100.htm">下一篇</a>]
<hr><p align="left"><small>发信人: clown (梧桐叶), 信区: UNP <br>
标 题: unp第二十三章学习体会 <br>
发信站: UNIX编程 (2001年10月16日21:09:29 星期二), 站内信件 <br>
<br>
本章并没有介绍有关socket的新内容,而是使用线程来实现以前的程序。 <br>
因此我也不准备多作介绍,而是转载一篇关于多线程编程的文章,以飨读者。 <br>
<br>
武汉白云黄鹤站∶精华区 <br>
发信人: clown (梧桐叶), 信区: LINUX <br>
标 题: [转载] [装载] Linux下的多线程编程 <br>
发信站: 武汉白云黄鹤站 (Tue Feb 13 16:06:02 2001), 站内信件 <br>
<br>
【 以下文字转载自 PUE 讨论区 】 <br>
【 原文由 clan 所发表 】 <br>
[说明]:由Infomagic转寄. <br>
<br>
Linux下的多线程编程 <br>
<br>
<br>
1 引言 <br>
线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是 <br>
在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个 <br>
进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术 <br>
已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。 <br>
为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么 <br>
的系统应该选用多线程?我们首先必须回答这些问题。 <br>
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我 <br>
们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的 <br>
数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运 <br>
行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启 <br>
动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换 <br>
所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销 <br>
大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区 <br>
别。 <br>
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立 <br>
的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很 <br>
不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据 <br>
可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些 <br>
问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可 <br>
能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。 <br>
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作 <br>
方式,当然有以下的优点: <br>
1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时, <br>
整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线 <br>
程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的 <br>
情况。 <br>
2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程 <br>
运行于不同的CPU上。 <br>
3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或 <br>
半独立的运行部分,这样的程序会利于理解和修改。 <br>
下面我们先来尝试编写一个简单的多线程程序。 <br>
<br>
2 简单的多线程编程 <br>
Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程 <br>
序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux <br>
下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统 <br>
调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有 <br>
关文档说明。下面我们展示一个最简单的多线程程序example1.c。 <br>
<br>
/* example.c*/ <br>
#include <stdio.h> <br>
#include <pthread.h> <br>
void thread(void) <br>
{ <br>
int i; <br>
for(i=0;i<3;i++) <br>
printf("This is a pthread.\n"); <br>
} <br>
<br>
int main(void) <br>
{ <br>
pthread_t id; <br>
int i,ret; <br>
ret=pthread_create(&id,NULL,(void *) thread,NULL); <br>
if(ret!=0){ <br>
printf ("Create pthread error!\n"); <br>
exit (1); <br>
} <br>
for(i=0;i<3;i++) <br>
printf("This is the main process.\n"); <br>
pthread_join(id,NULL); <br>
return (0); <br>
} <br>
<br>
我们编译此程序: <br>
gcc example1.c -lpthread -o example1 <br>
运行example1,我们得到如下结果: <br>
This is the main process. <br>
This is a pthread. <br>
This is the main process. <br>
This is the main process. <br>
This is a pthread. <br>
This is a pthread. <br>
再次运行,我们可能得到如下结果: <br>
This is a pthread. <br>
This is the main process. <br>
This is a pthread. <br>
This is the main process. <br>
This is a pthread. <br>
This is the main process. <br>
<br>
前后两次结果不一样,这是两个线程争夺CPU资源的结果。上面的示例中,我们使用 <br>
到了两个函数, pthread_create和pthread_join,并声明了一个pthread_t型的变量。 <br>
pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义: <br>
typedef unsigned long int pthread_t; <br>
它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为: <br>
extern int <br>
pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr, <br>
void *(*__start_routine) (void *), void *__arg)); <br>
第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数 <br>
是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数 <br>
thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这 <br>
样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程 <br>
成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和 <br>
EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数 <br>
代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的 <br>
函数,原来的线程则继续运行下一行代码。 <br>
函数pthread_join用来等待一个线程的结束。函数原型为: <br>
extern int pthread_join __P ((pthread_t __th, void **__thread_return)); <br>
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用 <br>
来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等 <br>
待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结 <br>
束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了; <br>
另一种方式是通过函数pthread_exit来实现。它的函数原型为: <br>
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__)); <br>
唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不 <br>
是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程 <br>
等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误 <br>
代码ESRCH。 <br>
在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数 <br>
pthread_create,pthread_join和pthread_exit。下面,我们来了解线程的一些常用属 <br>
性以及如何设置这些属性。 <br>
<br>
<br>
3 修改线程的属性 <br>
在上一节的例子里,我们用pthread_create函数创建了一个线程,在这个线程中, <br>
我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说, <br>
使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。 <br>
属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜 <br>
欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初 <br>
始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性 <br>
对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑 <br>
定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。 <br>
关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。 <br>
轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对 <br>
线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下, <br>
启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑 <br>
定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的 <br>
线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以 <br>
保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级 <br>
可以使得绑定的线程满足诸如实时反应之类的要求。 <br>
设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向 <br>
属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的) <br>
和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。 <br>
#include <pthread.h> <br>
pthread_attr_t attr; <br>
pthread_t tid; <br>
<br>
/*初始化属性值,均设为默认值*/ <br>
pthread_attr_init(&attr); <br>
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); <br>
<br>
pthread_create(&tid, &attr, (void *) my_function, NULL); <br>
<br>
线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们 <br>
采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结 <br>
束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的 <br>
系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了, <br>
线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状 <br>
态。设置线程分离状态的函数为 <br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -