首页 > 代码库 > 线程的一些概念

线程的一些概念

线程是操作系统能够进行运算调度的最小单位,包含在进程之中。典型的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

线程的一些概念