【Linux】线程创建等待终止分离
🌻个人主页:路飞雪吖~
🌠专栏:Linux
目录
一、Linux线程控制
✨POSIX线程库
✨创建线程
✨线程等待
🍔小贴士:
✨线程终止
✨分离线程
一、Linux线程控制
✨POSIX线程库
• 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的;
• 要使用这些函数库,要通过引入头文件<pthread.h>;
• 链接这些线程函数库时要使用编译器命令的 "-lpthread" 选项;
✨创建线程
在Linux内核中,没有线程的概念【没有单独设计tcb,只有进程pcb】!只有LWP,轻量级进程的概念,线程是使用LWP模拟的!这就意味着,Linux操作系统,不会给我们提供线程接口,只会提供创建轻量级进程的接口!
功能:创建⼀个新的线程
原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);参数:thread:返回线程IDattr:设置线程的属性,attr为NULL表⽰使⽤默认属性start_routine:是个函数地址,线程启动后要执⾏的函数arg:传给线程启动函数的参数返回值:成功返回0;失败返回错误码
错误检查:
• 传统的一些函数,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误;
• pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)而是将错误代码通过返回值返回;
• pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小;
为了服务上层用户,在操作系统和用户之间封装一层软件层【计算机当中任何问题,都可以新增一层软件层来完成】,封装一层软件层:把 clone() 系统调用封装成线程创建的接口,此时就可以使用库,库来完成所有线程的创建未来的管理,此时用户就不用关系 操作系统对于LWP的概念,只需要用线程相关的概念来进行上层代码的编写---- 用户级线程。
Window有真正的TCB,就能提供系统级别的进程和线程的创建接口。
<pthread.h> 用户级别的线程库【独立的库】,Linux系统自带的!原生线程库!
// Makefilemythread:mythread.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f mythread
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include<pthread.h>void *routine(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "我是新线程,我的名字是:" << name << std::endl;sleep(1);}return 0;
}int main()
{pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");if(n != 0)// 线程创建失败{std::cout << "create thread error:" << strerror(n) << std::endl;return 1;}while(true){std::cout << "我是main线程..." << std::endl;sleep(1);}
}
C++支持多线程,本质就是封装了pthread库!
//C++ 支持多线程#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <thread> // C++11 线程库int main()
{// 新线程std::thread t([](){while(true){std::cout << "我是新线程,我的名字是:new thread" << std::endl;sleep(1);}});// 主线程while(true){std::cout << "我是main线程..." << std::endl;sleep(1);} return 0;
}
其他高级语言也支持多线程,一定是 pthread 库,系统调用和库函数之间的关系,在库里面进行封装,保证代码的跨平台性。
🌠获得自己对应的线程id:
• 打印出来的 tid 是通过 pthread 库中有函数 pthread_self() 得到的,它返回⼀个 pthread_t 类型的
变量,指代的是调用 pthread_self() 函数的线程的 “ID”。
• 怎么理解这个“ID”呢?这个“ID”是 pthread 库给每个线程定义的进程内唯⼀标识,是 pthread 库
维持的。
• 由于每个进程有自己独立的内存空间,故此“ID”的作用域是进程级而非系统级(内核不认识)。
• 其实 pthread 库 也是通过内核提供的系统调用(例如clone)来创建线程的,而内核会为每个线程创建系统全局唯一的“ID”来唯一标识这个线程。
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>std::string toHex(pthread_t tid)
{char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *routine(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) << std::endl;sleep(1);}return 0;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, (void*)"thread-1");// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid: 0x%lx\n", tid);while(true){std::cout << "我是main线程..." << std::endl;sleep(1);}
}
• 线程tid,主要是能够区分对应的线程,线程具有唯一性;
• 1.新线程和main线程谁先运行,不确定【不同平台】
• 2.线程创建出来,要对进程的时间片进行瓜分
🌠小贴士:
使用PS命令查看线程信息
运行代码后执行:
• -L 选项:打印线程信息
• LWP 是什么呢?LWP 得到的是真正的线程ID。之前使用 pthread_self 得到的这个数实际上是一个地址,在虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线 程ID,线程栈,寄存器等属性。
• 在 ps -aL 得到的线程ID,有一个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在虚拟 地址空间的栈上,而其他线程的栈在是在共享区(堆栈之间),因为pthread系列函数都是pthread库 提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。
🌠一个进程内有多个执行流,多个线程执行同一个函数:这个函数被重入了!!
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>std::string toHex(pthread_t tid)
{// 4.进程内的函数,线程共享char buffer[64];// buffer在栈上开辟snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}// 被重入了!!!
void *routine(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) << std::endl;sleep(1);}return 0;
}int main()
{// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分pthread_t tid1;pthread_create(&tid1, nullptr, routine, (void *)"thread-1");// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid: 0x%lx\n", tid1);pthread_t tid2;pthread_create(&tid2, nullptr, routine, (void *)"thread-2");printf("new thread tid: 0x%lx\n", tid2);pthread_t tid3;pthread_create(&tid3, nullptr, routine, (void *)"thread-3");printf("new thread tid: 0x%lx\n", tid3);pthread_t tid4;pthread_create(&tid4, nullptr, routine, (void *)"thread-4");printf("new thread tid: 0x%lx\n", tid4);while (true){std::cout << "我是main线程..." << std::endl;sleep(1);}
}
多线程中,每一个线程都可以对向通一个显示器【文件】进行打印,前提条件是,所有线程都能看到同一个显示器文件【显示器文件 就相当于被当作一个公共资源】,多线程访问公共资源,本质上就是多线程在写入文件【各个线程自己写自己的】,而这个公共资源没有被保护,没有被保护的公共资源被多线程访问,而产生打印错乱,即为数据不一致问题;因而代码是有并发问题的!
• 3. 不加保护的情况下,显示器文件是共享资源!
• 4. 进程内的函数,线程共享;
• 线程都能看到,整个进程的任意一个方法【toHex()】,每个函数都要形成栈帧,栈帧结构都在自己的栈上。
一个线程只要把全局变量改了,另一个线程就能看到被修改的结果和变化。
• 5. 全局变量在线程内部是共享的;
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid)
{// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *routine1(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;gval++;sleep(1);}return 0;
}void *routine2(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(只检测):" << gval << std::endl;sleep(1);}return 0;
}int main()
{// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分pthread_t tid1;pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid: 0x%lx\n", tid1);pthread_t tid2;pthread_create(&tid2, nullptr, routine2, (void *)"thread-2");printf("new thread tid: 0x%lx\n", tid2);// pthread_t tid3;// pthread_create(&tid3, nullptr, routine, (void *)"thread-3");// printf("new thread tid: 0x%lx\n", tid3);// pthread_t tid4;// pthread_create(&tid4, nullptr, routine, (void *)"thread-4");// printf("new thread tid: 0x%lx\n", tid4);while (true){std::cout << "我是main线程..." << std::endl;sleep(1);}
}
【打印的结果不稳定,是进程调度产生的】
在多线程代码当中我们想让多个线程看到同一份资源是非常容易的,只要定义全局变量就可以了。
让线程2【routine2】崩溃,观察会发生什么现象:6. 一旦任何一个线程出现崩溃,会导致其他线程,包括主线程在内,全部都会退出。
多线程的弊端:一旦有一个线程崩溃,其他的线程就会全部的崩溃。
• 任何一个线程属于进程的一个执行分支,所以线程做任何事就是进程在做;
一旦某一个进程出现野指针,即查页表查失败 --> CPU内部的MMU直接报错 --> CPU内部触发软中断,OS中的全部的功能全部停下来,执行中断处理方法,根据软中断的中断号,直接查中断向量表,执行异常处理 --> 给目标进程发信号
6.1 异常的本质是信号,进程的多个线程是共享的,所以一旦来了一个异常信号,当前的OS会给每一个线程 设置异常处理。【信号是给进程的,进程中的每一个线程都会收到信号】
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid)
{// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *routine1(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;gval++;sleep(1);}return 0;
}void *routine2(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(只检测):" << gval << std::endl;sleep(1);// 6. 线程一旦出现问题,可能会导致其他线程其他线程全部崩溃// 6.1 异常的本质是信号int *p = nullptr;// 查页表失败 --> CPU内部的MMU报错 --> CPU触发软中断*p = 100;}return 0;
}int main()
{// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分pthread_t tid1;pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid1: 0x%lx\n", tid1);pthread_t tid2;pthread_create(&tid2, nullptr, routine2, (void *)"thread-2");printf("new thread tid2: 0x%lx\n", tid2);// pthread_t tid3;// pthread_create(&tid3, nullptr, routine, (void *)"thread-3");// printf("new thread tid: 0x%lx\n", tid3);// pthread_t tid4;// pthread_create(&tid4, nullptr, routine, (void *)"thread-4");// printf("new thread tid: 0x%lx\n", tid4);while (true){std::cout << "我是main线程..." << std::endl;sleep(1);}
}
一个进程内有多个线程,当有一个线程触发异常,一个进程里面全部的线程都会触发异常,如何知道这些线程是同一个进程里面的呢?pid 只有唯一性标识,不能很快的去查找【遍历所有进程的链表,效率太低了】,那该怎么办呢?
进程之间的关系:父子关系,兄弟关系,组关系。进程PCB里面有一张双链表,可以维护组关系,可以把进程里所有的LWP作为一个组,单独用一个小的链表去维护起来,因为任何一个PCB既可以属于调度队列,又可以属于等待队列,还可以属于其他数据结构。
✨线程等待
线程创建之后,谁先运行不确定,一般要保证主线程最后退出;进程创建之后,父子谁先运行不确定,一般要保证父进程最后退出,线程是主进程创建的。进程状态就是线程状态。
7. 线程创建之后,也是要被等待和回收的。
理由:
a. 等待原因【必要】:类似僵尸进程的问题【在系统层面,线程退出,这个线程的task_struct不敢随意释放;用户级线程 在线程库里面也要申请很多资源 不等待会造成内存泄漏的问题】
b. 回收原因【可选】:为了知道新线程的执行结果;
调用该函数的线程将挂起等待,直到 id 为 thread 的线程终中。thread 线程以不同的方法终止,通过 pthread_join 得到的终止状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ptr 所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用 pthread_cancel 异常终掉, value_ptr 所指向的单元里存放的是常数 PTHREAD_CANCELED【宏】。
3. 如果thread线程是自己调用 pthread_exit 终止的, value_ptr 所指向的单元存放的是传给 pthread_exit 的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传NULL给 value_ptr参数。
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid)
{// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *routine1(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;gval++;sleep(1);}return 0;
}int main()
{// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分pthread_t tid1;pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid1: 0x%lx\n", tid1);// 7. 线程创建之后,也是要被等待和回收的!// 7.1 理由:a. 类似僵尸进程的问题// b. 为了知道新线程的执行结果int n = pthread_join(tid1, nullptr);// 等待线程if(n != 0){std::cout << "join error:" << n << "," << strerror(n) << std::endl;return 1;}std::cout << "join success!" << std::endl;while (true){std::cout << "我是main线程..." << std::endl;sleep(1);}
}
新线程一直不退,主线程就pthread_join()一直阻塞等待:
让新线程阻塞1s后,执行break:线程等待成功!
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid)
{// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *routine1(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;gval++;sleep(1);break;}return 0;
}int main()
{// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分pthread_t tid1;pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");// 创建线程// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid1: 0x%lx\n", tid1);// 7. 线程创建之后,也是要被等待和回收的!// 7.1 理由:a. 类似僵尸进程的问题// b. 为了知道新线程的执行结果int n = pthread_join(tid1, nullptr);// 线程等待if(n != 0){std::cout << "join error:" << n << "," << strerror(n) << std::endl;return 1;}std::cout << "join success!" << std::endl;}
线程等待错误:pthread_join等待错误的线程tid。
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>// 5. 全局变量在线程内部是共享的
int gval = 100;// 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid)
{// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}void *routine1(void *args)
{std::string name = static_cast<const char *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << name << ", my tid is: " << toHex(pthread_self()) <<",全局变量(会修改):" << gval << std::endl;gval++;sleep(1);break;}return 0;
}int main()
{// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分pthread_t tid1;pthread_create(&tid1, nullptr, routine1, (void *)"thread-1");// 创建线程// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid1: 0x%lx\n", tid1);// 7. 线程创建之后,也是要被等待和回收的!// 7.1 理由:a. 类似僵尸进程的问题// b. 为了知道新线程的执行结果//int n = pthread_join(tid1, nullptr);// 线程等待int n = pthread_join(pthread_self(), nullptr);// 等待错误,等待的不是所产生的线程tid,而是自己本身的IDif(n != 0){std::cout << "join error:" << n << "," << strerror(n) << std::endl;return 1;}std::cout << "join success!" << std::endl;}
pthread_join这个线程若一直不退,主线程就会一直阻塞等待。
pthread_join默认是阻塞式的,让主线程阻塞时等待。
8. 线程传参问题:传递参数,可以是变量、数字、对象、结构体、类....
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>class ThreadData
{
public:ThreadData(const std::string &name, int a, int b):_name(name),_a(a),_b(b){}int Excute(){return _a + _b;}std::string Name(){return _name;}~ThreadData(){}
private:std::string _name;int _a;int _b;
};// 5. 全局变量在线程内部是共享的
int gval = 100; // 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid)
{// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer;
}// 被重入了!
void *routine1(void *args)
{// std::string name = static_cast<const char *>(args);ThreadData *td = static_cast<ThreadData *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << td->Name() << ", my tid is: " << toHex(pthread_self()) << ",全局变量(会修改):" << gval << std::endl;gval++;std::cout << "task result is :" << td->Excute() << std::endl;sleep(1);break;}return 0;
}int main()
{// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分// 8. 传参问题:传递参数,可以是变量、数字、对象pthread_t tid1;ThreadData *td = new ThreadData("thread-1", 10, 20);pthread_create(&tid1, nullptr, routine1, td); // 创建线程printf("new thread tid1: 0x%lx\n", tid1);// 7. 线程创建之后,也是要被等待和回收的!// 7.1 理由:a. 类似僵尸进程的问题// b. 为了知道新线程的执行结果int n = pthread_join(tid1, nullptr);// 线程等待// int n = pthread_join(pthread_self(), nullptr); // 等待错误,等待的不是所产生的线程tid,而是自己本身的IDif (n != 0){std::cout << "join error:" << n << "," << strerror(n) << std::endl;return 1;}std::cout << "join success!" << std::endl;
}
🍔小贴士:
#include <iostream> #include <string> #include <cstdio> #include <cstring> #include <unistd.h> #include <pthread.h> #include <thread>class ThreadData { public:ThreadData(const std::string &name, int a, int b):_name(name),_a(a),_b(b){}int Excute(){return _a + _b;}std::string Name(){return _name;}~ThreadData(){} private:std::string _name;int _a;int _b; };// 5. 全局变量在线程内部是共享的 int gval = 100; // 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid) {// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer; }// 被重入了! void *routine1(void *args) {// std::string name = static_cast<const char *>(args);ThreadData *td = static_cast<ThreadData *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << td->Name() << ", my tid is: " << toHex(pthread_self()) << ",全局变量(会修改):" << gval << std::endl;gval++;std::cout << "task result is :" << td->Excute() << std::endl;sleep(1);break;}// return 0;return (void*)10;// 线程退出方式:1、线程入口函数retur n,表示线程退出 }int main() {// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分// 8. 传参问题:传递参数,可以是变量、数字、对象pthread_t tid1;ThreadData *td = new ThreadData("thread-1", 10, 20);pthread_create(&tid1, nullptr, routine1, td); // 创建线程// pthread_create(&tid1, nullptr, routine1, (void *)"thread-1"); // 创建线程// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid1: 0x%lx\n", tid1);// 7. 线程创建之后,也是要被等待和回收的!// 7.1 理由:a. 类似僵尸进程的问题// b. 为了知道新线程的执行结果void *ret = nullptr;// 线程所对应的返回值【线程routine1的返回值(void*)10】int n = pthread_join(tid1, &ret);// 线程等待// int n = pthread_join(pthread_self(), nullptr); // 等待错误,等待的不是所产生的线程tid,而是自己本身的IDif (n != 0){std::cout << "join error:" << n << "," << strerror(n) << std::endl;return 1;}std::cout << "join success!, ret: " << (long long int)ret << std::endl;}
理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!
• 传参问题:传递参数,可以是变量、数字、对象
• 返回值问题:返回参数,可以是变量、数字、对象
#include <iostream> #include <string> #include <cstdio> #include <cstring> #include <unistd.h> #include <pthread.h> #include <thread>class ThreadData { public:ThreadData(const std::string &name, int a, int b):_name(name),_a(a),_b(b){}void Excute(){_result = _a + _b;}int Result(){return _result;}std::string Name(){return _name;}~ThreadData(){} private:std::string _name;int _a;int _b;int _result; };// 5. 全局变量在线程内部是共享的 int gval = 100; // 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid) {// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer; }// 被重入了! void *routine1(void *args) {// std::string name = static_cast<const char *>(args);ThreadData *td = static_cast<ThreadData *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << td->Name() << ", my tid is: " << toHex(pthread_self()) << ",全局变量(会修改):" << gval << std::endl;gval++;td->Excute();// std::cout << "task result is :" << td->Excute() << std::endl;sleep(1);break;}return td; }int main() {// 1. 新线程和main线程谁先运行,不确定// 2. 线程创建出来,要对进程的时间片进行瓜分// 8. 传参问题:传递参数,可以是变量、数字、对象pthread_t tid1;ThreadData *td = new ThreadData("thread-1", 10, 20);// 主线程申请的堆空间pthread_create(&tid1, nullptr, routine1, td); // 创建线程// pthread_create(&tid1, nullptr, routine1, (void *)"thread-1"); // 创建线程// std::cout << "new thread tid:" << tid << std::endl;printf("new thread tid1: 0x%lx\n", tid1);// 7. 线程创建之后,也是要被等待和回收的!// 7.1 理由:a. 类似僵尸进程的问题// b. 为了知道新线程的执行结果// void *ret = nullptr;// 线程所对应的返回值【线程routine1的返回值(void*)10】ThreadData *rtd = nullptr;int n = pthread_join(tid1, (void**)&rtd);// 我们可以保证,执行完毕,任务一定处理完了,结果变量一定已经被写入了!// int n = pthread_join(pthread_self(), nullptr); // 等待错误,等待的不是所产生的线程tid,而是自己本身的IDif (n != 0){std::cout << "join error:" << n << "," << strerror(n) << std::endl;return 1;}std::cout << "join success!, ret: " << rtd->Result() << std::endl;delete td; }
🌠创建多线程与等待,如何把任务进行处理:
#include <iostream> #include <string> #include <cstdio> #include <cstring> #include <unistd.h> #include <pthread.h> #include <thread>class ThreadData { public:ThreadData(){}void Init(const std::string &name, int a, int b){_name = name;_a = a;_b = b;}void Excute(){_result = _a + _b;}int Result(){ return _result; }std::string Name(){ return _name; }void SetID(pthread_t tid) { _tid = tid; }pthread_t ID() { return _tid; }int A() { return _a; }int B() { return _b; }~ThreadData(){} private:std::string _name;int _a;int _b;int _result;pthread_t _tid; };// 5. 全局变量在线程内部是共享的 int gval = 100; // 被所有线程共享【在全局数据区】std::string toHex(pthread_t tid) {// 4. 进程内的函数,线程共享char buffer[64];snprintf(buffer, sizeof(buffer), "0x%lx", tid);return buffer; }// 被重入了! void *routine1(void *args) {// std::string name = static_cast<const char *>(args);ThreadData *td = static_cast<ThreadData *>(args);while (true){// 3. 不加保护的情况下,显示器文件就是共享资源!std::cout << "我是新线程,我的名字是:" << td->Name() << ", my tid is: " << toHex(pthread_self()) << ",全局变量(会修改):" << gval << std::endl;gval++;td->Excute();// std::cout << "task result is :" << td->Excute() << std::endl;sleep(1);break;}// return 0;// 8. 返回值问题:返回参数,可以是变量、数字、对象!// 8.1 理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!// return (void*)10;// 线程退出方式:1、线程入口函数retur n,表示线程退出return td; }#define NUM 10int main() {ThreadData td[NUM];// 准备我们要加工处理的数据for(int i = 0; i < NUM; i++){char id[64];td[i].Init(id, i*10, i*20);}// 创建多线程for(int i = 0; i < NUM; i++){pthread_t id;pthread_create(&id, nullptr, routine1, &td[i]);td[i].SetID(id);}// 等待多线程for(int i = 0; i < NUM; i++){pthread_join(td[i].ID(), nullptr);}// 汇总处理结果for(int i = 0; i <NUM; i++){printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].ID());} }
在多线程当中所有的东西都是共享的,线程有独立的栈,所以线程里面的局部变量,不能被其他线程看到;但是想要被看到也是有方法的。
在线程的代码里,不同线程定义的临时变量,是在不同的栈上的。
#include <iostream> #include <string> #include <cstdio> #include <cstring> #include <unistd.h> #include <pthread.h> #include <thread>int *addr = nullptr;// 把线程内部的地址传出去,其他线程就可以看到void* start1(void *args) {std::string name = static_cast<const char*>(args);int a = 100;addr = &a;while (true){std::cout << name << "local val a : " << a << std::endl;sleep(1);} }void* start2(void *args) {std::string name = static_cast<const char*>(args);while (true){if(addr != nullptr)std::cout << name << "local val a : " << (*addr)++ << std::endl;sleep(1);} }int main() {pthread_t tid1, tid2;// 创建进程pthread_create(&tid1, nullptr, start1, (void*)"thread-1");pthread_create(&tid2, nullptr, start2, (void*)"thread-2");//进程等待pthread_join(tid1, nullptr);pthread_join(tid2, nullptr); }
✨线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1、从线程函数 return。这种方法对主线程不适用,从main函数return相当于调用exit。
2、 线程可以调用 pthread_exit 终止自己。
功能:线程终止
原型: void pthread_exit(void *value_ptr);
参数: value_ptr:value_ptr不要指向⼀个局部变量。
返回值: ⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)
3、一个线程可以调用 pthread_cancel 终止同一进程中的另一个线程【取消线程,一定是目标线程已经启动了】
功能:取消⼀个执⾏中的线程
原型: int pthread_cancel(pthread_t thread);
参数: thread:线程ID
返回值:成功返回0;失败返回错误码
• 9. 新线程return,表示该线程退出;主线程return,表示进程结束!
• 任何地方调用exit,表示进程退出!
• pthread_exit() <==> return
需要注意,pthread_exit() 或者 return 返回的指针所指向的内存单元必须是 全局的 或者 是用malloc分配的, 不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>void *start(void *args)
{std::string name = static_cast<const char *>(args);while (true){// std::cout << name << "local val a : " << (*addr)++ << std::endl;sleep(1);break;}// return 0;// 9. 新线程return,表示该线程退出// exit(1);// 任何地方调用exit,表示进程退出!pthread_exit((void*)10);
}int main()
{pthread_t tid;// 创建进程pthread_create(&tid, nullptr, start, (void *)"thread-1");// 进程等待void *ret = nullptr;pthread_join(tid, &ret);std::cout << "new tjread exit code: " << (long long int)ret << std::endl;return 0;// 主线程return,表示进程结束!
}
• pthread_cancel 在写多线程代码的时候,有些线程不想要了可以取消掉;但是并不建议使用这个函数,因为在取消的时候目标线程是什么工作状态我们并不清楚。
new tjread exit code: -1 :说明我们取消一个线程,我们依旧要 pthread_join() ,线程退出 若不等待,就会变成类似僵尸进程的问题。
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>void *start(void *args)
{std::string name = static_cast<const char *>(args);while (true){// std::cout << name << "local val a : " << (*addr)++ << std::endl;std::cout << "I am a new thread" << std::endl;sleep(1);// break;}// return 0;// 9. 新线程return,表示该线程退出// exit(1);// 任何地方调用exit,表示进程退出!pthread_exit((void*)10);
}int main()
{pthread_t tid;// 创建进程pthread_create(&tid, nullptr, start, (void *)"thread-1");sleep(5);pthread_cancel(tid);std::cout << "取消线程:" << tid << std::endl;sleep(5);// 进程等待void *ret = nullptr;pthread_join(tid, &ret); // PTHREAD_CANCELED;std::cout << "new tjread exit code: " << (long long int)ret << std::endl;return 0;// 主线程return,表示进程结束!
}
✨分离线程
我主线程也要做自己的事情呢?
可以不等待新线程 --- 将目标线程设置为分离状态!
线程被等待状态:
1. pthread_join():线程需要被join(默认)
2. pthread_detach:线程分离(主线程不需要等待新线程【类似分家】)
注意:在多执行流情况下,主执行流是最后退出的!
线程一旦被分离,就不能 pthread_join() 。
• 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏。
• 如果不关心线程的返回值,join是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>void *start(void *args)
{// pthread_detach(pthread_self()); // 自己把自己分离,主线程 pthread_join()失败,进程不会阻塞,直接return退出std::string name = static_cast<const char *>(args);while (true){std::cout << "I am a new thread" << std::endl;sleep(1);// break;}pthread_exit((void*)10);
}int main()
{pthread_t tid;// 创建进程pthread_create(&tid, nullptr, start, (void *)"thread-1");// pthread_detach(tid); // 把目标线程进行分离,pthread_join() 也是失败的,进程不会阻塞,直接return退出sleep(5);// 进程等待void *ret = nullptr;int n = pthread_join(tid, &ret); // PTHREAD_CANCELED;std::cout << "new tjread exit code: " << (long long int)ret << ", n: " << n << std::endl;return 0;// 主线程return,表示进程结束!
}
在任何一个线程调用 exec*()【进程的程序替换】,是不可以的。当我们在进行进程的程序替换的时候,exec* () 是会把整个进程全部给替换掉的【代码和数据全部替换】,代码和数据有可能其他线程也在用,有可能会导致其他线程全部崩掉。
若非要进行程序替换,就可以在线程里面进行 fork() 创建子进程。任何一个线程,创建子进程 fork() 会把当前进程的地址空间、页表、代码和数据 全部拷贝一份,只不过 默认形成的新进程内部只有一个PCB【子进程的PCB】,线程内可以创建进程,再让子进程去调用exec*()。
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <thread>void *start(void *args)
{pid_t id = fork();if(id == 0){//...}
}int main()
{pthread_t tid;// 创建进程pthread_create(&tid, nullptr, start, (void *)"thread-1");// pthread_detach(tid); // 把目标线程进行分离,pthread_join() 也是失败的,进程不会阻塞,直接return退出sleep(5);// 进程等待void *ret = nullptr;int n = pthread_join(tid, &ret); // PTHREAD_CANCELED;std::cout << "new tjread exit code: " << (long long int)ret << ", n: " << n << std::endl;return 0;// 主线程return,表示进程结束!
}
在进程内可以创建 线程,线程内也可以创建进程。
如若对你有帮助,记得关注、收藏、点赞哦~ 您的支持是我最大的动力🌹🌹🌹🌹!!!
若有误,望各位,在评论区留言或者私信我 指点迷津!!!谢谢 ヾ(≧▽≦*)o \( •̀ ω •́ )/