一 线程基本概念:
(一)注意:
1.线程在进程内执行环境必需的信息:线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。
*一组并发线程运行在一个进程的上下文中,每个线程都有自己独立的线程上下文:线程ID、栈、栈指针、程序计数器、条件码和通用目的的寄存器值。每个线程和和其它线程共享进程上下文的剩余部分:整个用户虚拟地址空间(代码、读/写数据、堆、已经所有共享库代码和数据区),也共享同样的打开文件集合。
2.进程内的所有信息对该进程内的所有线程都是共享的:包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。
3.Pthreads引入了一种全新的报错方式:Pthreads中的函数通过返回值表示错误状态,而没有使用errno变量。
//出错不会导致程序终止,所以我们需检测每一个函数的返回值(自己领悟)。
Pthreads在提供了一个在线程内的errno变量,只是为了和使用errno的现有函数兼容。每个线程都有属于自己的局部errno以避免一个线程干扰另一个线程。
4.函数中的无类型指针参数(void *ptr)能可以传递多个值,可以用该指针指向一个结构体。
(二)线程标识:
(1)线程ID:
*线程ID只在它所属的进程环境中有效和唯一,进程ID在整个系统唯一。
*线程ID的类型为pthread_t,可移植的操作系统实现不能把它作为整数。
(2)相关函数:
*pthread_self():获取线程ID,返回调用线程的线程ID。
*pthread_equal(pthread_t tid1,pthread_t tid2):比较两个线程ID是否一样。
(三)线程创建:
1.每个进程只有一个控制线程,程序刚开始运行时,是以单进程中的单个控制线程启动的,在创建多个控制线程之前,程序的行为与传统的进程没有什么区别。
2.通过pthread_create创建线程:
*函数原型,编译和链接时需-lpthread:
int pthread_create(pthread_t *tid,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg)
*tid:tid指向的内存单元被设置为新创建线程的线程ID。
*attr:用来定制各种不同的线程属性。(不定制时,设置为NULL)
*start_routine:新创建的线程从start_routine函数的地址开始运行,还函数只有一个无类型指针参数arg。
*成功时返回0;出错时返回一个错误代码,并且*tid未被定义。
*errors:
EAGAIN:Insufficient resources to create another thread,or a system-imposed limit on the number of threads was encountered.
EINVAL:Invalid settings in attr.
EPERM:No permission to set the scheduling policy and parameters specified in attr.
*通常写法:
if( ( err=pthread_create(...) ) !=0)//不设置errno变量。
{调用strerror(err)返回错误代码的信息。线程没有perror函数可用}
3.线程创建时不能保证哪个线程会先执行。
4.linux中使用clone系统调用来实现pthread_create。
*clone系统调用创建子进程,这个子进程可以共享父进程一定数量的执行环境,这个数量是可配置的。
(四)线程终止:
1.单个线程,在不终止整个进程的情况下终止的方式:
*线程从启动例程返回,返回值是线程的退出码。
*线程可以被同一进程的其他线程取消(pthread_cancel)。
*线程调用pthread_exit。
* 主线程从main函数返回(或者return)时会终止子线程,若主线程调用pthread_exit()终止的话则不会终止子线程。
注意:如果进程中任意函数调用exit、_exit或_Exit,那么整个进程就会终止。
2.相关函数:
*pthread_exit:
void pthread_exit(void *retval):终止调用线程,并返回retval作为线程的返回值,其他同进程中的线程可以用pthread_join函数访问到这个指针。如果对线程返回值不感兴趣,则可以把retval设为NULL,这样pthread_join只等待指定线程终止,不获取线程的终止状态。
*pthread_join:
int pthread_join(pthread_t thread,void **retval):调用线程一直阻塞,等待thread指定的线程终止,thread指定的线程必须是joinable的。
*pthread_detach(使线程进入分离状态):
原型:
int pthread_detach(pthread_t tid):使线程进入分离状态。
默认情况下,线程的终止状态会保存到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时就立即释放。
当线程是分离状态时,不能使用pthread_join等待它的终止状态,若调用返回错误(EINVAL)。
*pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate):设置线程属性detachstate。
detachstate:PTHREAD_CTREATE_DETACH(分离状态启动)或PTHREAD_CREATE_JOINABLE(正常状态启动)。
*pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate):获取当前的线程属性detachstate。
*pthread_cancel:
线程可以通过pthread_cancel来请求取消同一进程中的其他线程。
*int pthread_cancel(pthread_t tid):发送一个取消请求给线程tid,是否或何时目标线程对取消请求响应取决于线程的两个属性:可取消状态和可取消类型。
*pthread_cancel不等待线程终止,在默认情况下,线程在取消请求发出以后继续运行,直到线程到达某个取消点。取消点:线程检查是否被取消并按照请求进行动作的一个位置。某些函数在调用时,取消点都会出现,也可以调用pthread_testcancel自己添加取消点。
*线程清理处理程序:
线程可以建立多个处理程序,处理程序记录在栈中,所以执行顺序与注册顺序相反。
void pthread_cleanup_push(void (*rtn)(void *),void *arg)//arg为清理函数的参数。
void pthread_cleanup_pop(int execute)
线程执行以下动作时,会调用清理函数:
~调用pthread_exit。
~响应取消请求。
~用非0参数执行pthread_cleanup_pop。
注意:
*如果参数execute为0,pthread_cleanup_pop只删除上一个清理函数,而不执行。
*线程从启动例程返回而终止的话,不调用线程清理处理程序。
*pthread_cleanup_push与pthread_cleanup_pop必须成对出现,否则编译不通过。
*pthread_cleanup_push注册一个回调函数,如果你的线程在对应的pthread_cleanup_pop之前异常退出(return是正常退出,其他是异常),那么系统就会执行这个回调函数(回调函数要做什么你自己决定)。但是如果在pthread_cleanup_pop之前没有异常退出,pthread_cleanup_pop就把对应的回调函数取消了。
二 线程执行模型:
(一)多线程执行模型在某些方面与多进程的执行模型是相似的。每个进程开始生命周期时都是单一线程,这个线程称为主线程。
(二)多线程执行与多进程执行的不同:
1.线程的上下文要比进程上下文小得多,所以线程的上下文切换要比进程的上下文切换快的多。
2.线程不像进程那样,不是按照严格的父子层次来组织。和一个进程相关的线程组成一个对等(线程)池。主线程和其它线程的区别仅在于主线程是进程中第一个运行的线程。对等(线程)池的主要影响是:一个线程可以杀死它的任意对等线程,或者等待它的任意对等线程终止,每个对等线程可以读写相同的共享数据。