6、线程分离
- 默认情况下,新创建的线程是
joinable
的,线程退出后,需要对其进行pthread_join
操作,否则无法释放资源,从而造成系统泄漏。 - 如果不关心线程的返回值,
join
是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
线程分离的函数:
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离,joinable
和分离是冲突的,一个线程不能既是joinable
又是分离的。
下面我们进行线程分离以后再进行等待,看看会发生什么。
#include <iostream> #include <cstdio> #include <unistd.h> #include <cstring> #include <pthread.h> void* threadRoutine(void* args) { int cnt = 3; while (cnt--) { std::cout << "new thread run..." << std::endl; } return nullptr; } int main() { pthread_t t1; pthread_create(&t1, nullptr, threadRoutine, nullptr); // 线程分离 pthread_detach(t1); // 线程等待 int error = pthread_join(t1, nullptr); if (error == 0) { std::cout << "等待成功" << std::endl; } else { std::cout << "等待失败,错误码" << error << ",错误原因:" << strerror(error) << std::endl; } return 0; }
由于我们对新线程进行了线程分离,所以主线程等待错误,将错误信息进行打印,主线程退出,新线程也要跟着退出。
三、Linux线程数据
1、共享与私有的对比
我们知道线程共享进程数据,但也拥有自己的一部分私有数据:
- 线程ID。
- 一组寄存器。(存储每个线程的上下文信息)
- 栈。(每个线程都有临时的数据,需要压栈出栈)
- errno。(C语言提供的全局变量,每个线程都有自己的)
- 信号屏蔽字。
- 调度优先级。
进程的多个线程共享 同一地址空间,因此代码段、数据段都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
- 文件描述符表。(进程打开一个文件后,其他线程也能够看到)
- 每种信号的处理方式。(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
- 当前工作目录。(pwd)
- 用户ID和组ID。
2、Linux线程库的深入理解
我们知道Linux
线程库是一个动态库,当我们多线程程序在运行时动态库需要被加载到共享区内的。
创建出来的线程是要被进行管理的,由于Linux
不提供真正的线程,只提供LWP
,也就意味着操作系统只需要对内核执行流LWP
进行管理,而供用户使用的线程接口等其他数据,应该由线程库自己来管理,因此管理线程的工作就应该在线程库里进行。
于是线程库里面为我们提供了一个个类似于tid的结构,这个结构里面包含了struct_pthread
(存储了线程的许多属性),线程局部存储, 线程栈(这就是为什么每个线程都有独立的栈结构,主线程用的是进程系统栈,新线程用的是库中提供的栈),我们以前常用的pthread_t
类型存储的值就是这个类似于tid结构的首地址
下面一个代码将pthread_t
转换为16进制的地址:
#include <iostream> #include <cstdio> #include <unistd.h> #include <string> #include <pthread.h> // 16进制转换 std::string HexAdress(pthread_t tid) { char str[64]; snprintf(str, sizeof(str), "0x%x", tid); std::string s (str); return s; } void* threadRoutine(void* args) { int cnt = 3; while (cnt--) { std::cout << "new thread aderss : " << HexAdress(pthread_self()) << std::endl; sleep(1); } return nullptr; } int main() { pthread_t t1; pthread_create(&t1, nullptr, threadRoutine, nullptr); // 线程分离 pthread_detach(t1); sleep(5); return 0; }
实际上我们线程创建对应的pthread_creat
使用的就是clone
系统调用,从前到后参数的意义以此是:要执行的函数,独立栈的地址,创建的一些选项,传给参数1
的函数参数。
__thread
是GCC
内置的线程局部存储设施,__thread
修饰的全局变量(内置类型)每一个线程有一份独立实体,各个线程的值互不干扰,因为它们都被存储到了线程库管理结构中的线程局部存储内。
#include <iostream> #include <cstdio> #include <unistd.h> #include <pthread.h> // __thread修饰全局变量 __thread int g_val = 10; void* threadRoutine(void* args) { long long a = (long long)args; int cnt = 3; while (cnt--) { std::cout << "线程" << a << ",g_val : " << (g_val++ * a) << std::endl; sleep(1); } return nullptr; } int main() { pthread_t t1, t2, t3; pthread_create(&t1, nullptr, threadRoutine, (void*)1); usleep(1000); pthread_create(&t2, nullptr, threadRoutine, (void*)2); usleep(1000); pthread_create(&t3, nullptr, threadRoutine, (void*)3); // 线程分离 pthread_detach(t1); pthread_detach(t2); pthread_detach(t3); sleep(5); return 0; }