线程的创建、等待、分离、终止
本文所实现的所有线程均是POSIX标准的用户级线程。
一. POSIX线程库
1.与线程有关的函数构成了一个系列,绝大多数的名字都是以“pthread_”打头的
2.要使用这些函数库,必须引入头文件“#include<pthread.h>"
3.链接这些线程函数库时要使用编译器命令的“-lpthread”选项
二. 线程控制
1. 创建线程
(1)函数原型:
(2)函数功能:创建一个新线程
(3)函数参数:
1)thread:输出型参数,用于返回新创建的线程id
2)attr:设置线程的属性,为NULL时表示使用默认属性
3)start_routine:函数指针,指向线程启动后要执行的函数
4)arg:传给线程启动后要执行函数的参数
(4)函数返回值:成功返回0,失败返回错误码
(5)测试代码:
//线程的创建 #include <stdio.h> #include <pthread.h> #include <unistd.h> void* thread_run(void* arg)//传入线程创建的函数 { while(1) { printf("I am thread 1\n"); sleep(2); } } int main() { pthread_t tid;//无符号长整型 //新线程去执行thread_run函数 int ret = pthread_create(&tid, NULL, thread_run, "thread 1");//这里给thread_run传入的参数为一个字符串 //这之后的是主线程在执行 while(1) { printf("I am main thread\n"); sleep(1); } return 0; }
运行结果:
可以看到,该程序创建好之后,有两个执行流,主线程执行一个循环,新线程执行一个循环。这样的情况在单线程的程序结果中是不可能出现的。
2.线程等待
与进程等待类似,线程也需要等待。因为退出的线程空间不会被释放,仍然在进程的地址空间中,且创建其他新的线程不会复用已退出线程的地址空间。
(1)函数原型:
(2)函数功能:主线程等待新线程结束退出,这里是阻塞式等待
(3)函数参数:
1)thread:线程id
2)retval:输出型参数,用于存放线程的退出信息
(4)函数返回值:成功返回0,失败返回错误码
注意,thread函数以不同方式终止,得到的终止状态是不同的:
若thread线程通过return返回,retval指向的单元里存放的是thread线程函数的返回值;
若thread线程被别的函数调用pthread_cancel异常终止掉,retval指向的单元里存放的是常数PTHREAD_CANCELED,其中,该常数是(void*)-1;
若
thread线程是自己调用pthread_exit终止的,retval指向的单元里存放的是传给pthread_exit的参数;
若对thread线程的退出信息不在意,可传入NULL。
(5)测试代码:
//线程的等待 #include <stdio.h> #include <pthread.h> #include <unistd.h> void* thread_run(void* arg)//传入线程创建的函数 { //pthread_self函数用于获得当前线程id printf("I am %s, id is %lu\n", (char*)arg, pthread_self()); sleep(2); return ((void*)132); } int main() { pthread_t tid;//无符号长整型 //新线程去执行thread_run函数 int ret = pthread_create(&tid, NULL, thread_run, "thread 1");//这里给thread_run传入的参数为一个字符串 //这之后的是主线程在执行 printf("new: thread id id %lu\n", tid); printf("main: thread id id %lu\n", pthread_self()); void* ptr; //这里阻塞式等待 pthread_join(tid, &ptr); printf("main thread run, new thread ret: %d\n", (int)ptr); return 0; }
运行结果:
可以看到,主线程等待,看到新线程的退出码为132。
3. 线程分离
默认状态下,新创建的线程是joinable的,线程退出后,要对其进行等待操作,否则无法释放资源,从而造成系统泄漏。但是,若系统不关心新创建线程的返回值,等待是一种负担,这种时候我们就可以对新创建的线程进行分离操作。分离后的线程,在退出时,会自动释放系统资源。
(1)函数原型:
(2)函数功能:分离线程,使得在其退出后,自动释放系统资源
(3)函数参数:
thread:要进行分离操作的线程id
(4)函数返回值:成功返回0,失败返回错误码
注:
可以是线程组内其他进程对目标进程进行分离,也可以是线程自己分离;
joinable与分离是冲突的,线程不可能又是joinable又是分离的;
不论线程怎么折腾,只有线程出现异常,无论是否分离,进程都会出问题。
(5)测试代码:
//所有线程的pid都是进程的pid,均相同 #include <stdio.h> #include <pthread.h> #include <unistd.h> void* thread_run(void* arg)//传入线程创建的函数 { const char* msg = (const char*)arg; printf("%s: tid:%#x, pid:%d\n", msg, pthread_self(), getpid()); sleep(3); //pthread_detach(pthread_self());//自己分离自己 //只要线程异常,不论是否分离,都会影响进程 int *a; *a = 10; } int main() { pthread_t tid;//无符号长整型 pthread_create(&tid, NULL, thread_run, "thread 1"); //分离后再join,就会出错 pthread_detach(tid); while(1) { printf("I am main thread\n"); sleep(1); } ////sleep(2); //int ret = pthread_join(tid, NULL);//不关心线程退出信息 //printf("ret: %d\n", ret); return 0; }
运行结果:
可以看到,即使我们分离了新线程,进程最后还是会出现段错误。
4. 线程终止
如果要终止线程而不终止进程,我们有三种办法:
(1)从线程函数调用return。这种方法对主线程不适用,因为从main函数调用return相当于进程退出;
(2)调用pthread_exit终止自己。不适用于main函数,main函数必须要以进程方式退出;
1)函数原型:
2)函数功能:使得线程终止
3)函数参数:
retval:
该线程退出时的退出码,即该线程调用函数start_routine的返回值
注意:参数retval不要指向一个局部变量,因为当线程的调用函数退出时,局部变量也销毁了。
4)函数返回值:无返回值,跟线程一样,线程自己终止自己时无法返回到它的调用者(自身)
5)测试代码:
//线程的终止 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> void* thread_run(void* arg)//传入线程创建的函数 { //pthread_self函数用于获得当前线程id printf("I am %s, id is %lu\n", (char*)arg, pthread_self()); pthread_exit((void*)99); //exit(123);//exit用于进程退出 } int main() { pthread_t tid;//无符号长整型 //新线程去执行thread_run函数 int ret = pthread_create(&tid, NULL, thread_run, "thread 1");//这里给thread_run传入的参数为一个字符串 //这之后的是主线程在执行 void* ptr; //这里阻塞式等待,获得线程退出信息 pthread_join(tid, &ptr); printf("main thread run, new thread ret: %d\n", (int)ptr); return 12; }
运行结果:
(3)一个线程可以调用pthread_cancel函数终止同一进程中的另一线程
1)函数原型:
2)函数功能:取消一个执行中的线程,尽量不要用于取消自己
3)函数参数:
thread:线程id
4)函数返回值:成功返回0,失败返回错误码
5)测试代码:
//线程的终止 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> void* thread_run(void* arg)//传入线程创建的函数 { //pthread_self函数用于获得当前线程id printf("I am %s, id is %lu\n", (char*)arg, pthread_self()); pthread_cancel(pthread_self());//线程取消自己 //sleep(3); //pthread_exit((void*)99); //exit(123);//exit用于进程退出 } int main() { pthread_t tid;//无符号长整型 //新线程去执行thread_run函数 int ret = pthread_create(&tid, NULL, thread_run, "thread 1");//这里给thread_run传入的参数为一个字符串 //这之后的是主线程在执行 //sleep(2); //pthread_cancel(tid);//取消线程 void* ptr; //这里阻塞式等待,获得线程退出信息 pthread_join(tid, &ptr); printf("main thread run, new thread ret: %d\n", (int)ptr); return 12; }
运行结果:
若是在新线程的执行函数中取消自己,运行结果为:
若是让主线程取消新创建的线程,运行结果为:
注意:
1.pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是malloc分配的,不能在线程函数的栈上分配,因为当其他线程函数得到这个返回指针的时候线程函数已经退出了;
2.exit用于进程退出,并不能用于线程退出;