首页 > 代码库 > 线程的一些概念
线程的一些概念
线程是操作系统能够进行运算调度的最小单位,包含在进程之中。典型的UNIX/Linux进程可以看做只有一个控制线程。
线程包含了表示进程内执行环境必须的信息,其中包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据(没有独立的堆,共享堆)。进程内所有信息对该进程内的线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。
线程标识
像进程ID一样,每个线程也有一个线程ID。进程ID在整个系统中是唯一的,但线程ID只在它所属的进程内有效。线程ID是用pthread_t数据类型表示,实现的时候可以用一个数据结构来代表pthread_t数据类型,所以在可移植的操作系统不能把它作为整数处理。因此两个线程ID比较的话,必须是用函数
#include<pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
获取自己ID可以用
#incude<pthread.h>
pthread_t pthread_self(void);
线程创建
在创建线程先,可以认为进程中只有一个主控线程。创建线程可以使用
#include<pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr.
void *(*start_rtn)(void *), void *restrict arg);
当线程创建成功返回时,tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性。
新创建的线程从start_rtn函数地址开始运行,该函数之后一个无类型指针参数arg,如果要传入多个参数,那么就要把多个参数做成结构体,然后传入结构体。
线程创建后不能保证哪个线程先运行:是新创建的还是调用线程。新创建的线程可以访问进程的地址空间,继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。
pthread失败时会返回错误码,不像其他POSIX函数一样设置errno。每个线程都提供errno副本,这只是为了与使用errno的现有函数兼容。
可以通过下面代码测试一下,功能为打印线程ID
#include<pthread.h> pthread_t ntid; void printids(const char *s) { pid_t pid; pthread_t tid; pid=getpid(); tid=pthread_self(); printf("%s pid %u tid %u (0x%x)\n",s,(unsigned int)pid,(unsigned int)tid,(unsigned int)tid); } void* thr_fn(void *arg) { printids("new thread:"); return ((void*)0); } int main(void) { int err; err=pthread_create(&ntid,NULL,thr_fn,NULL); if(err!=0) printf("can't create thread:%s\n",strerror(err)); printids("main thread:"); sleep(1); exit(0); }
在Fedora上面运行结果为:
main thread: pid 3922 tid 3078109440 (0xb7783900)
new thread: pid 3922 tid 3078105920 (0xb7782b40)
sleep(1)的目的是防止主线程结束。通过pthread_self获取线程ID,因为在pthread_create在返回前,函数可能就已经运行了。
线程终止
一个进程中的任何一个线程调用exit,_exit,_Exit整个进程都会终止。如果信号默认动作是终止进程,那么,把该信号发送给线程也会终止进程。
终止单个线程可以通过以下方法:
1、线程只是从启动例程中返回,返回值是线程退出码。
2、线程被同一进程中的其他线程取消。
3、调用pthread_exit
#include<pthread.h>
void pthread_exit(void *rval_ptr)
其中rval_ptr是一个无类型指针,和传给启动例程参数类似。同一进程中的其他进程可以调用pthread_join函数获得这个指针。
#include<pthread.h>
void pthread_join(pthread_t thread, void **rval_prt);
其中要注意一点,就是线程退出状态的值,不能再线程的栈上。如果在栈上,当线程退出后,栈空间就释放了。
线程可以取消同一进程中的其他线程
#include<pthread.h>
int pthread_cancel(pthread_t tid)
线程可以注册在退出时调用的清理函数,类似进程的atexit,处理程序记录在栈中,因此调用顺序与注册顺序相反
#inlcude<pthread.h>
void pthread_cleanup_push(void (*rtn) (void *), void *arg);
void pthread_cleanup_pop(int execute);
写代码测试一下上面的几个函数:
#include<pthread.h> void cleanup(void *arg) { printf("cleanup:%s\n",(char *)arg); } void* thr_fn1(void *arg) { printf("thread 1 start\n"); pthread_cleanup_push(cleanup,"thread 1 first handler"); pthread_cleanup_push(cleanup,"thread 1 second handler"); printf("thread 1 push complete\n"); if(arg) pthread_exit((void*)1); pthread_cleanup_pop(0); pthread_cleanup_pop(0); pthread_exit((void*)1); } void* thr_fn2(void *arg) { printf("thread 2 start\n"); pthread_cleanup_push(cleanup,"thread 2 first handler"); pthread_cleanup_push(cleanup,"thread 2 second handler"); printf("thread 2 push complete\n"); if(arg) return (void*)2; pthread_cleanup_pop(0); pthread_cleanup_pop(0); return (void*)2; } int main(void) { pthread_t tid1, tid2; void *tret; pthread_create(&tid1,NULL,thr_fn1, (void *)1);//创建线程1 pthread_create(&tid2,NULL,thr_fn2, (void *)1);//创建线程2 //获取线程退出状态 pthread_join(tid1, &tret);//阻塞,直到线程1退出 printf("thread 1 exit code %d\n", (int)tret);//直接把指针转换为int pthread_join(tid2, &tret);//阻塞,直到线程2退出 printf("thread 2 exit code %d\n", (int)tret); exit(0); }
在Fedora上面运行结果为:
thread 2 start
thread 2 push complete
thread 1 start
thread 1 push complete
cleanup:thread 1 second handler
cleanup:thread 1 first handler
thread 1 exit code 1
thread 2 exit code 2
线程的一些概念