线程

1. 线程概念

典型的UNIX进程可以看成只有一个控制线程:一个线程在同一时刻只做一件事情。每个线程在进行事件处理时可以采用同步编程模式,同步编程模式比异步编程模式简单得多。在只有一个控制线程的情况下,单个进程需要完成多个任务时,实际上需要把这些任务串行化;有了多个控制线程,相互独立的任务的处理可以交叉进行,只需要为每个任务分配一个单独的线程,当然只有在处理不相互依赖的情况下,两个任务的执行才可以穿插进行。

线程包含了表示进程内执行环境必须的信,岂止包括进程中标识线程的线程ID、一组寄存器、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全部内存和堆内存、栈以及文件描述符。

 

2. 线程ID

每个线程有一个线程ID,线程iD只在它所属的进程环境中有效。 

#inlcude <pthread.h>

int pthread_equal (pthread_t tid1, pthread_t tid2);

pthread_t phread_self(void);

当线程需要识别以线程ID作为标识的数据结构时,phread_self函数(获得自身的线程ID)可以与pthread_equal(比较线程ID是否相等)函数一起使用。

 

3. 线程创建

在POSIX线程(pthread)的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的,在创建多个控制线程以前,程序的行为与传统的进程没有什么区别。

#inlcude <pthread.h>

int pthread_create (pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void*(*start_rtn)(void), void * restrict arg);

当pthread成功返回时,由tidp指向的内存单元被置为新创建线程的线程ID。参数attr用于定制各种不同的线程属性。默认为NULL。

新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构体中,然后把这个结构的地址作为arg参数传入。

注意:pthread函数调用失败时通常会返回错误码,它们并不像其它的POSIX一样设置errno。每个线程都提供errno的副本,这只是为了与使用errno的现有函数兼容。

 

4. 线程终止

进程的任意线程调用了exit、_Exit或_exit,那么整个进程就会终止。与此同时,如果信号的默认动作是终止进程,那么把该信号发送到线程会终止整个进程。

单个线程有下列三种方式退成,在不终止整个进程的情况下停止它的控制流。

1) 线程只是从启动例程中返回,返回值是线程的退出码

2) 线程可以被统一进程中的其他线程取消

3) 线程调用pthread_exit。

#include <pthread.h>

void pthread_exit(void * rval_ptr);

int pthread_join(pthread_t thread, void ** rval_ptr);

rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。

调用pthread_join函数的调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或被取消。如果线程只是从它的启动例程返回,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。

可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果对线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。

注意:pthread_create和pthread_exit函数的无类型指针参数能传递的数值不止一个,该指针可以传递包含更多复杂信息的结构体地址,但是注意这个结构体所使用的内存在调用者完成调用以后必须任然有效,否则就会出现无效或非法内存访问。例如:一是在调用线程的栈上分配了该结构,那么其他的线程在使用这个结构时内存内同可能已经改变了;二是线程在自己的栈上分配了一个结构然后把指向这个结构的指针传给了pthread_exit,那么当调用pthread_join的线程试图使用该结构时,这个栈有可能已经被撤销,这块内存已经另作他用了。

线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他线程。

#include <pthread.h>

void pthread_cancel(pthread_t tid);

在默认的情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如何调用了参数PTHREAD_CANCEL的pthread_exit函数。但是,线程可以选择忽略取消方式或控制取消方式。pthread_cancel函数并不等待线程终止,它仅仅提出请求。

线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序(thread cleanup handler)。线程可以建立多个清理处理程序。

#include <pthread.h>

void pthread_cleanup_push(void (*trn)(void *),void *arg);

void pthread_cleanup_pop(int execute);

当线程执行以下动作时调用清理函数,调用参数为arg,清理函数rtn的调用顺序是由pthread_cleanup_push函数来安排的。

1) 调用pthread_exit时

2) 响应取消请求时

3) 用非零execute参数调用pthread_cleanup_pop时

如果execute参数置为0,清理函数将不被调用。也就是说,如果线程是通过从它的启动例程中返回而终止的话,那么它的清理处理函数程序就不会被调用。

在默认情况下,线程的终止状态会保存在对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。当线程被分离时,并不能调用pthread_join函数等待它的终止。函数pthread_detach调用可以用于使线程进入分离状态。

#include <pthread.h>

void pthread_detach(pthread_t tid);

下表是进程原语与线程原语的比较:

进程原语

线程原语

描述

fork

pthread_create

创建新的控制流

exit

pthread_exit

从现有的控制流中退出

waitpid

pthread_join

从控制流中得到退出状态

atexit

pthread_cancel_push

注册在退出时调用的函数

getpid

pthread_self

获取控制流的ID

abort

pthread_cancel

请求控制流的非正常退出

5. 实例(pthread_exit参数的不正确使用)

在Linux上运行此程序,得到:

#./a.out

thread 1:

structure at 0x308b2abc

private.level = 2

private.age = 24

private.app = 1

parent startring second thread

thread 2:ID is 42760

parent:

structure at 0x308b2abc

private.level = 0

private.age = 42760

private.app = 132710

 

可以看出,当主线程访问这个结构时,结构的内容(在线程tid_fst的栈上分配)已经改变。注意,第二个线程是如何覆盖第一线程的栈的。为了解决这个问题,可以使用全局结构,或者用malloc函数分配结构。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值