Linux基础内容(24) —— 线程概念_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131294692?spm=1001.2014.3001.5501
目录
1.线程操作
1.线程创建问题
2.线程终止问题
1.exit退出
2.pthread_exit退出
3.直接退出
3.线程等待问题
信号问题
4.线程取消
5.线程分离
2.理解线程库
重新认识线程库
1.语言层面
2.结构设计
3.pthread_id的含义
1.线程操作
1.线程创建问题
class ThreadData { public: pthread_t tid; char namebuffer[64]; }; void *start_routine(void *args) { ThreadData *td = static_cast<ThreadData *>(args); // 安全的强制类型转换 int cnt = 10; while (cnt) { cout << "new thread success, name: " << td->namebuffer << " cnt: " << cnt << endl; sleep(1); } delete td; return nullptr; } int main() { vector<ThreadData*> tids; #define NUM 10 for (int i = 0; i < NUM; i++) { ThreadData *td = new ThreadData(); snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i); pthread_create(&td->tid, nullptr, start_routine, (void *)td); tids.push_back(td); } while (true) { cout << "new thread success, name: main thread" << endl; sleep(1); } return 0; }
1.我们创造了10个线程同时进入同一个函数,该函数为可重入状态。start_routine就是可重入函数
2.此时的函数是可重入函数,因为没有出现问题。这些cnt都是临时变量,在各自的线程之中,不会互现影响,也反映了线程有自己的独立栈结构
3.循环式的创造线程不能直接循环,这样可能内部的缓冲区没有被修改,线程就创造了一堆,使得每一个线程的信息都一样
2.线程终止问题
1.exit退出
由于线程的健壮性问题,我们在任意一个执行流中调用ecit函数,都会使得整个进程退出。因为exit针对的对象为进程,我们的信号发送给进程后,自然进程结束,线程也跟着结束。
2.pthread_exit退出
3.直接退出
如果线程执行结束,直接在函数中return。操作系统会自动回收线程
3.线程等待问题
其实线程跟进程一样都需要在结束后等待操作系统回收。如果不等待,线程对应的PCB也没有被释放,那么就会出现类似于进程的僵尸进程的内存漏泄问题。不过可以回收线程释放的信息,只释放线程。
void *start_routine(void *args) { ThreadData *td = static_cast<ThreadData *>(args); // 安全的强制类型转换 int cnt = 10; while (cnt) { cout << "new thread success, name: " << td->namebuffer << " cnt: " << cnt << endl; sleep(1); } //delete td; return nullptr; } for(auto &iter : threads) { int n = pthread_join(iter->tid, nullptr); assert(n == 0); cout << "join : " << iter->namebuffer << " success " << endl; delete iter; } //注意为了让已经释放空间的线程能打出其对应的名字,我们需要把start_routine中delete td进行删除 //因为我们不想要早其打印之前就被销毁,那么只能在其打印后进行释放
对指定的thread进行等待,viod**为二级指针是线程的返回的参数。
其实线程join等待回收能将线程exit的数据进行回收
class ThreadData { public: int number; pthread_t tid; char namebuffer[64]; }; void *start_routine(void *args) { ThreadData *td = static_cast<ThreadData *>(args); // 安全的强制类型转换 int cnt = 10; while (cnt) { cout << "new thread success, name: " << td->namebuffer << " cnt: " << cnt << endl; sleep(1); // int *p = nullptr; // p=NULL; //*p=0; } //delete td; return (void*)td->number; } for(auto &iter : threads) { void* ret=nullptr; int n = pthread_join(iter->tid, &ret); assert(n == 0); cout << "join : " << iter->namebuffer << " success, number: " <<(int_least32_t)ret<< endl; delete iter; }
这样就能接受到信息了。当然我们也可以返回一个结构体,这样调回来的就是结构体的信息了。
信号问题
线程出问题,不会收到信号,因为如果线程挂了,进程都退出了。那么也就意味着join等待不会考虑信号的问题。
4.线程取消
线程能被其他线程取消
5.线程分离
1.线程的等待功能只有阻塞等待
2.如果线程释放时,我们需要看到线程的返回信息,那么join的函数就可以实现我们需要的
3.如果我们不想要线程的信息,也就意味着join的回收信号是无意义的
4.那么我们不能像进程那样非阻塞等待,是否可以像线程类似通过信号进行自动回收,答案是肯定的,线程分离就能将线程自动被操作系统回收
pthread_self:哪个线程调用,就得到哪个线程的pthread_t
std::string changeId(const pthread_t& pthread_id) { char tid[128]; snprintf(tid,sizeof(tid),"0x%x",pthread_id); return tid; } void *start_routine(void *args) { std::string threadname = static_cast<const char *>(args); // 安全的强制类型转换 while (true) { std::cout << threadname << " running ...: " << changeId(pthread_self()) << std::endl; sleep(1); } } int main() { pthread_t tid; pthread_create(&tid, nullptr, start_routine, (void *)"thread 1"); std::string main_id=changeId(pthread_self()); std::cout << "main thread running ...: " << changeId(tid) << std::endl; pthread_join(tid, nullptr); return 0; }
两个地址完全一致,说明知道指向的线程地址是多少
pthread_detach:分离一个指定的线程
2.理解线程库
1.重新认识线程库
1.语言层面
mypthread:mypthread.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f mypthread
#include <iostream> #include <unistd.h> #include <thread> void thread_run() { while (true) { std::cout << "我是新线程..." << std::endl; sleep(1); } } int main() { std::thread t1(thread_run); while (true) { std::cout << "我是主线程..." << std::endl; sleep(1); } t1.join(); return 0; }
若这样,编译器编不过,系统提示我们没有包含pthread库
1.C++11的多线程在linux的环境中其实是对pthread库的封装,也就意味着,c++创造的线程在linux下依然是轻量级进程
2.不管是什么语言,在linux的环境下都会调用pthread库。
2.结构设计
1.linux没有真正意义上的线程,只有轻量级进程。也就意味着linux提供的调用函数为轻量级进程
2.但是作为用户不会考虑linux的环境和设计,需要的是线程
3.pthread库提供的线程库就是调用linux对轻量级进程的调用函数的封装。那么也就是说其实我们使用的线程其实是库提供给我们的
4.既然库提供给我们对于的结构了,那么我们使用时一定是被管理的。那么库给我们提供的pthread就有其属性,以至于用来记录所有线程的信息
5.pthread_attr_t就是存储线程属性的结构联合体,线程的属性比进程的属性要少。
6.上面的设计其实就是用户及线程,即用户关心的线程属性在库中,内存提供了线程的函数会调用
7.用户级线程与内核轻量级进程一一对应,达到管理的作用
3.pthread_id的含义
1.在虚拟内存地址中,对应的栈就是主线程的栈
2.线程库其实就是磁盘的文件,它在被调用后会在虚拟内存的共享区中出现
3.共享区中存储着线程结构体的地址,而多个线程则是通过数组的形式排列存储在虚拟空间
4.共享区指向动态库找到映射的线程,线程中存储有局部存储和独立栈
5.那么我们就能进一步理解所谓的线程库就是封装了linux中的clone轻量级进程
6.增加__thread在内置类型全局变量前,会使得内置类型转换为线程的局部存储中,每一个线程都来一份
__thread int cnt = 0;