什么是线程
在一个程序中多个执行路线就叫做线程(thread)。更准确的定义是:线程是一个进程内部的一个控制序列。
弄清楚fork系统调用和创建新线程之间的区别:
当程序执行fork调用时当程序执行fork调用时,将创建出该进程的一份新副本.这个进程拥有自己的变量和自己的PID,它的时间调度也是独立的,它的执行几乎完全独立于父进程。当在进程中创建一个新线程时,新的执行线程将拥有自己的栈(因此也有自己的局部变量)但与它的创建者共享全局变量,文件描述符,信号处理函数和当前目录状态。
线程优点和缺点
在某些环境中,创建新线程要比创建新进程有更明显的优势,新线程的创建代价要比进程小的多。 线程有下面的一些优点:
1.有时,让程序看起来好像是在同时做两件事情是很有用的。一个经典的例子是,在编辑文档的同时对文档中的单词个数进行实时统计。一个线程负责处理用户的输入并执行文本编辑工作,另一个(它也可以看到相同的文档内容)则不断刷新单词计数变量.第一个进程通过这个共享的计数变量让用户随时了解自己的工作进展情况。另一个例子是一个多线程的数据库服务器,这种一种明显的单进程服务多用户的情况.它会在响应一些请求的同时阻塞另外一些请求,使之等待磁盘操作,从而改善整体上的数据吞吐量.对于数据库服务器来说,这个明显的多任务工作如果用多进程的方式来完成将很难做到高效,因为各个不同的进程必须紧密合作才能满足加锁和数据一致性方面的需求,而用多线程来完成就比用多进程要容易的多。
2.一个混杂着输入,计算和输出的应用程序,可以将这几个部分分离为3个线程来执行,从而改善程序执行的性能。当输入或输出线程需要等待连接时,另外一个线程可以继续执行。因此,如果一个进程在任一时刻最多只能做一件事情的话,线程可以让它在等待连接之类的事情的同时做一些其他有用的事情。一个需要同时处理多个网络连接的服务器应用程序也是天生适合用于应用多线程的例子。
3.一般而言,线程之间的切换需要操作系统做的工作要比进程之间的切换少的多,因此多个线程对资源的需求要远小于多个进程。如果一个程序在逻辑上需要有多个执行线程,那么在单处理器系统上把它运行为一个多线程程序才更符合实际情况。
线程也有下面一些缺点:
1.编写多线程程序需要非常仔细的设计,在多线程程序中,因时序上的细微偏差或无意造成的变量共享而引发错误的可能性是很大的。
2.对多线程程序的调试要比单线程程序的调试困难的多,因为线程之间的交互非常难于控制。
3.将大量计算分为两个部分,并把这两个部分作为两个不同的线程来运行的程序在一台单处理器机器上并不一定运行的更快,除非计算确实允许它的不同的部分可以被同时计算,而且运行它的机器拥有多个处理器核来支持真正的多线程处理。
第一个线程程序
线程有一套完整的与其相关的函数库调用,它们中的绝大多数函数名都以pthread_开头。为了使用这些函数库调用,必须定义宏_REENTRANT,在程序中包含头文件pthread.h,并且在编译程序时需要用选项-lpthread来链接线程库。
在设计最初的UNIX和POSIX库例程时,人们假设每个进程中只有一个执行线程,一个明显的例子就是errno,该变量用于获取某个函数调用失败后的错误信息。在一个多线程程序里,默认情况下,只有一个errno变量供所有的线程共享。在一个线程准备获取刚才的错误代码时,该变量很容易被另一个线程中的函数所改变。类似的问题还存在与fputs之类的函数中,这些函数通常用一个全局性区域来缓存输出数据。 为了解决这个问题,需要使用被称为可重入的例程.可重入代码可以被多次调用而仍然正常工作,这些调用可以来自不同的线程,也可以是某种形式的嵌套调用.因此,代码中的可重入部分通常只使用局部变量,这使得每次对该代码的调用都将获得它自己的唯一的一份数据副本。 编写多线程程序时,通过定义宏_REENTRANT来告诉编译器需要可重入功能,这个宏的定义位于程序中的任何#include语句之前,它将做3件事情,并且做的非常优雅,以至于一般不需要知道它到底做了哪些事。
1.它会对部分函数重新定义它们的可安全重入的版本,这些函数的名字一般不会发生改变,只是会在函数名后面添加_r字符串。例如,函数名gethostbyname将变为gethostbyname_r。
2.stdio.h中原来以宏的形式实现的一些函数将变成可安全重入的函数 。
3.在errno.h中定义的变量errno现在将成为一个函数调用,它能够以一种多线程的安全的方式来获取真正的errno值 在程序中包含头文件pthread.h还将提供一些其他的将在代码中使用到的定义和函数原型,就如同在头文件stdio.h为标准输入和标准输入例程所提供的定义一样。最后,需要确保在程序中包含了正确的头文件,并且在编译程序时链接了实现pthread函数的正确的线程库。