您的位置:首页 > 编程语言

多线程编程1 --线程创建,退出,等待,分离

2017-06-11 23:26 471 查看

线程的基本概念

线程是在进程内部运行的一个执行分支,它是运行在进程的地址空间中。

和进程之间的独立性不同,线程之间更强调共享性,它共享进程的地址空间,比如数据段,代码段,堆。。。以及文件描述符表。因此,无论是线程的创建,终止,切换的代价都要比进程小很多,线程之间可以直接通信而不需要向进程通信那么麻烦(共享内存,消息队列,信号量等机制),当然有优点就有缺点,由于线程强调共享性,一个进程内的所有线程是互相影响的, 一个线程崩溃会导致该进程内的所有线程都崩溃。

线程虽然强调共享性,但是也不是所有东西都共享,对于每个线程来说,它都私有自己的栈,私有自己运行上下文如寄存器,栈指针,线程id,调度优先级等等。

在操作系统课程上,我们讲,进程是资源分配的基本单位,线程是CPU调度的基本单位,确实如此,进程负责控制资源,线程之间调度能够更好的实现并发性。

在不同的平台下,对线程实现是不一样的,例如在Windows下是有着类似于PCB控制进程的TCB(线程控制块)来控制线程,而在Linux下没有真正意义上的线程,它是用进程模拟实现,所以Linux下的线程有时也叫轻量级进程(LWP),这样的好处是,增强了代码的复用性,使得线程能够复用进程的数据结构,维护性也相对好了起来。

线程的创建

我们所学习的多线程编程都是基于Linux平台的,我自己主要的参考书籍就是Unix高级环境编程(APUE),关于线程编程主要用的POSIX标准下的pthread库,在引用这个库时候,需要

#include<pthread>

//编译需要加上-lpthread
gcc -o thread thread.c -lpthread


每个线程都有线程id,假设我们叫线程id为tid,tid可以用一个pthread_t的数据类型表示,需要注意的是,pthread _t在不同的平台下实现不同,比如在Linux为无符号整型,其他为一个结构体,因此,获取tid最好用对应的函数,判断两个tid是否相等也最好用pthread库提供的接口以提供程序的移植性。

int pthread_equal(pthread_t t1, pthread_t t2); //相等返回非0,不等返回0

pthread_t pthread_self(void);//获取线程id


线程创建是用pthread_create函数实现的

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start) (void *), void *arg);


thread参数指向的内存单元被设置为新创建线程的tid

attr为线程属性,不用时可设置为NULL

start为函数指针,指向一个函数,新创建的线程从start函数的地址处开始运行

arg是配合start函数,用于向start函数传递参数,如果不需要传递参数可设置为NULL,如果有多个参数,则需要将这些参数放在一个结构体,然后传递指向结构体的指针。

需要注意的是,在一个进程刚开始运行时候,可以看作是这个进程是一个只有单线程的进程,我们可以把这个单线程看作是主线程,当使用pthread_create创建一个新线程并不保证主线程还是新线程的运行次序,也就是谁先谁后并不知道。

线程的终止

线程的终止有几种方式,比如暴力的直接终止进程(调用exit,或者发送终止信号给进程),由于线程是运行在进程的地址空间的,因此当进程终止,线程不得不终止。

我们主要讨论的是终止一个进程中的单个线程而不对其他线程产生影响。

线程的终止方式:

线程函数自己返回(调用return),返回值为线程的退出码,但是这种方式对主线程不管用,因为从main函数return相当于exit

调用pthread_exit返回

线程可被一个进程内的其他线程取消

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

对于1,2两种方式,他们返回的都是一种void*指针

//return (void*)1;
//void pthread_exit(void *retval);


对于第3种方式,其他线程可调用pthread_cancel函数取消某个线程=

int pthread_cancel(pthread_t thread);


默认情况下,该函数使得tid为thread的线程表现出像自己调用pthread_exit函数一样,但是,线程可以选择忽略取消方式或者是控制取消方式,注意pthread _ cancel并不等待线程终止,它仅仅是请求

线程的等待

线程可以被终止,也就可以被等待,等待需要调用下面的函数:

int pthread_join(pthread_t thread, void **retval);
//从thread线程中,接收到thread的返回码保存到retval中




实例:

介绍了这么多函数,我们下面写一个小程序来看看这些函数具体是如何使用的?

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void* thread1(void* x)
{
printf("thread1_return : pid = %u, tid = %u\n", getpid(), (unsigned int)pthread_self());
return (void*)1;
}

void* thread2(void* x)
{
printf("thread2_exit : pid = %u, tid = %u\n", getpid(), (unsigned int)pthread_self());
pthread_exit((void*)2);
}

void* thread3(void* x)
{
printf("thread3_cancel : pid = %u, tid = %u\n", getpid(), (unsigned int)pthread_self());
while (1)
{
printf("thread3 needs to be cancelled\n");
sleep(1);
}
}

int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;

void* ret1;
void* ret2;
void* ret3;

pthread_create(&tid1, NULL, thread1, NULL);
pthread_join(tid1, &ret1);
printf("thread %u, return code is %d\n", (unsigned)tid1, (int)ret1);

pthread_create(&tid2, NULL, thread2, NULL);
pthread_join(tid2, &ret2);
printf("thread %u, exit code is %d\n", (unsigned)tid2, (int)ret2);

pthread_create(&tid3, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid3);
pthread_join(tid3, &ret3);
printf("thread %u, cancel code is %d\n", (unsigned)tid3, (int)ret3);

return 0;
}


最后结果如下:

thread1_return : pid = 7800, tid = 3076094784
thread 3076094784, return code is 1

thread2_exit : pid = 7800, tid = 3076094784
thread 3076094784, exit code is 2

thread3_cancel : pid = 7800, tid = 3076094784
thread3 needs to be cancelled
thread3 needs to be cancelled
thread3 needs to be cancelled
thread 3076094784, cancel code is -1


我们创建了三个线程,通过线程的三种终止方式,我们展示了如何使用之前我们所说的函数~

线程的分离

一般来说,线程终止,其他线程可调用pthread_join函数来获取退出状态,但是线程也可以设置为分离状态(detach),这样的线程一旦退出就立刻回收它的所有的资源,因此不能对一个已经detach状态的线程调用pthread _join。

对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也就是说,不能对同一线程调用两次pthread_ join, 或者如果已经对一个线程调用 了pthread_detach就不能再调⽤用pthread_join了。

关于分离线程

在任一时间点,一个线程都是可结合的或者可分离的。可结合的线程可以被其他线程回收资源并杀死,在被其他资源回收之前,它资源是不释放的。可分离的线程则不能被其他线程回收资源,当它结束时,它的资源由系统自动释放。



线程的清理处理程序

线程可以安排它退出时候需要调用的函数,这与进程退出用atexit注册需要调用的函数是类似的,这样的函数成为线程清理处理程序

线程可以建立多个清理处理程序,记录在栈中,也就是说它的执行顺序和注册顺序相反。

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

void pthread_cleanup_pop(int execute);


当线程执行以下操作时,调用清理函数clean,调用参数为arg,clean调用顺序有push函数安排:

调用pthread_exit

响应取消请求

用非零execute参数调用pthread_cleanup _pop

如果execute函数为0,清理函数将不会被调用。无论什么情况,pop都会删除上次push调用简历建立的清理处理程序。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/types.h>

void clean_up(void* x)
{
printf("clean_up : %s\n", (char*)x);
}

void* thread1(void* arg)
{
printf("thread1 starts\n");
pthread_cleanup_push(clean_up, "thread1 handler 1");
pthread_cleanup_push(clean_up, "thread1 handler 2");
printf("thread1 ends\n");

if (arg != NULL)
return (void*)1;
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return (void*)1;
}

void* thread2(void* arg)
{
printf("thread2 starts\n");
pthread_cleanup_push(clean_up, "thread2 handler 1");
pthread_cleanup_push(clean_up, "thread2 handler 2");
printf("thread2 ends\n");

if (arg != NULL)
pthread_exit((void*)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);

pthread_exit((void*)2);
}

int main()
{
pthread_t tid1;
pthread_t tid2;

void* ret1;
void* ret2;

pthread_create(&tid1, NULL, thread1, (void*)1);
pthread_create(&tid2, NULL, thread2, (void*)2);

pthread_join(tid1, &ret1);
pthread_join(tid2, &ret2);

printf("thread1 exit code is %d\n", (int)ret1);
printf("thread2 exit code is %d\n", (int)ret2);
return 0;
}


运行结果如下:

thread2 starts

thread2 ends

clean_up : thread2 handler 2

clean_up : thread2 handler 1

thread1 starts

thread1 ends

thread1 exit code is 1

thread2 exit code is 2

可以看出,两个线程都成功退出,但是只调用了第二个线程的清理程序,所以,如果线程是通过从它的启动例程中返回(也就是上面的return语句),则不调用对应的线程清理处理程序

PS:关于线程函数和进程函数的对比可看下图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐