每个进程都拥有自己的数据段、代码段和堆栈段,这就造成进程在进行创建、切换、撤销操作时,需要较大的系统开销。
为了减少系统开销,从进程中演化出了线程。
线程存在于进程中,共享进程的资源。
线程是进程中的独立控制流,由环境(包括寄存器组和程序计数器)和一系列的执行指令组成。
每个进程有一个地址空间和一个控制线程。
调度:
线程是CPU调度和分派的基本单位。
拥有资源:
进程是系统中程序执行和资源分配的基本单位。
线程自己一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈),但它可以去访问其所属进程的资源,如进程代码段,数据段以及系统资源(已打开的文件,I/0设备等)。
系统开销:
同一个进程中的多个线程可共享同一地址空间,因此它们之间的同步和通信的实现也变得比较容易。
在进程切换时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置;而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作,从而能更有效地使用系统资源和提高系统的吞吐量。
并发性:
不仅进程间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行。
多任务程序的设计:
一个程序可能要处理不同应用,要处理多种任务,如果开发不同的进程来处理,系统开销很大,数据共享,程序结构都不方便,这时可使用多线程编程方法。
并发程序设计:
一个任务可能分成不同的步骤去完成,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥,同步并发完成。这样可以为不同的任务步骤建立线程。
网络程序设计:
为提高网络的利用效率,我们可能使用多线程,对每个连接用一个线程去处理。
数据共享:
同一个进程中的不同线程共享进程的数据空间,方便不同线程间的数据共享。
在多CPU系统中,实现真正的并行
就像每个进程都有一个进程号一样,每个线程也有一个线程号。
进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进程环境中有效。
进程号用pidt数据类型表示,是一个非负整数。线程号则用pthreadt数据类型来表示。
有的系统在实现pthreadt的时候,用一个结构体来表示,所以在可移植的操作系统实现不能把它做为整数处理
#include
int pthread_creat(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
功能:创建一个线程。
参数:
thread:线程标识符地址。
attr:线程属性结构体地址。
start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值:
成功:返回0
不成功:返回非0
与fork不同的是pthread_create创建的线程不与父线程在同一点开始运行,而是从指定的函数开始运行,函数运行完后,该线程也就退出了。
线程依赖进程存在的,如果创建线程的进程结束了,线程也就结束了。
线程函数的程序在pthread库中,故链接时要加上参数-lpthread。
测试代码:
#include
#include
#include
void * thread_fun1(void * arg)
{
while(1)
{
printf("This is thread 1\n");
sleep(1);
}
}
void * thread_fun2(void * arg)
{
while(1)
{
printf("This is thread 2\n");
sleep(1);
}
}
int main(int argc, char **argv[])
{
pthread_t tid1,tid2;
int ret;
ret = pthread_create(&tid1, NULL, thread_fun1, NULL);
if(ret != 0)
{
perror("pthread_creat");
return 0;
}
ret = pthread_create(&tid2, NULL, thread_fun2, NULL);
if(ret != 0)
{
perror("pthread_creat");
return 0;
}
while(1);
return 0;
}
#include
int pthread_join(pthread_t thread, void * *retval);
功能:
等待子线程结束,并回收子线程资源。
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址。
创建一个线程后应回收其资源,但使用pthread_join函数会使调用者阻塞,故Linux提供了线程分离函数。
#include
int pthread_detach(pthread_t thread);
功能:
使调用线程与当前进程分离,使其成为一个独立的进程,该进程终止时,系统将自动回收它的资源。
参数:
thread:线程号。
在进程中我们可以调用exit函数或exit函数来结束进程,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。
1、线程从执行函数中返回。
2、线程调用pthread exit退出线程。
3、线程可以被同一进程中的其它线程取消。
#include
void pthread_exit(void *retval);
功能:
退出调用线程
参数:
retval:存储线程退出状态的指针。
注:一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
pthread cancel函数的实质是发信号给目标线程thread,使目标线程退出。
此函数只是发送终止信号给目标线程,不会等待取消目标线程执行完才返回。
然而发送成功并不意味着目标线程一定就会终止,线程被取消时,线程的取消属性会决定线程能否被取消及何时被取消。
线程的取消状态
即线程能不能被取消
线程取消点
即线程被取消的地方
线程被取消后,该线程并不是马上终止,默认情况下线程执行到取消点时才能被终止。编程时可以通过pthread testcancel函数设置线程的取消点。
void pthread testcancel(void);
当别的线程取消调用此函数的线程时候,被取消的线程执行到此函数时结束。
线程的取消类型
在线程能被取消的状态下,是立马被取消结束还是执行到取消点的时候被取消结束
线程被取消后,该线程并不是马上终止,默认情况下线程执行到消点时才能被终止。编程时可以通过pthread setcanceltype函数设置线程是否可以立即被取消。
pthread setcanceltype(int type,int*oldtype);
type:
PTHREADCANCEL ASYNCHRONOUS:立即取消
PTHREADCANCEL_DEFERRED:不立即被取消
oldtype:
保存调用线程原来的可取消类型的内存地址。
线程的取消状态
在Linux系统下,线程默认可以被取消。编程时可以通过pthread setcancelstate函数设置线程是否可以被取消。
pthread setcancelstate(int state.int*old_state);
state:
PTHREAD CANCEL DISABLE:不可以被取消
PTHREAD CANCEL ENABLE:可以被取消。
old state:
保存调用线程原来的可取消状态的内存地址。
和进程的退出清理一样,线程也可以注册它退出时要调用的函数,这样的函数称为线程清理处理程序(thread cleanup handler)
注意:
线程可以建立多个清理处理程序。
处理程序在栈中,故它们的执行顺序与它们注册时的顺序相反。
注册清理函数
#include
void pthread_cleanup_push(void (* routine)(void *), void *arg);
功能:
将清除函数压栈。即注册清理函数。
参数:
routine:线程清理函数的指针。
arg:传给线程清理函数的参数。
弹出清理函数
#include
void pthread_cleanup_pop(int execute);
功能:
将清除函数弹栈,即删除清理函数。
参数:
execute:线程清理函数执行标志位。
非0,弹出清理函数,执行清理函数。
0,弹出清理函数,不执行清理函数。
当线程执行以下动作时会调用清理函数:
1.调用pthread_exit退出进程。
2.响应其他线程的取消请求。
3.用非零excute调用pthread_cleanup_pop。
无论哪种情况pthread_cleanup_pop都将删除上一次pthread_cleanup_push调用注册的清理处理函数。push和pop必须成对出现。