当前位置: 首页 > ai >正文

【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  \( •̀ ω •́ )/

http://www.xdnf.cn/news/15434.html

相关文章:

  • 力扣25.7.15每日一题——有效单词
  • Vue框架之模板语法(插值表达式、指令系统、事件处理和表单绑定)全面解析
  • CSS的初步学习
  • 课题学习笔记1——文本问答与信息抽取关键技术研究论文阅读(用于无结构化文本问答的文本生成技术)
  • elasticsearch 下载/安装
  • AJAX 入门到精通
  • 60V/3A高效同步降压转换器替代LM2576只需4个元器件
  • 终端安全管理系统为什么需要使用,企业需要的桌面管理软件
  • Video Python(Pyav)解码一
  • MongoDB基础增删改查命令
  • CSS :root伪类详解:实现动态主题切换的关键所在
  • CAS单点登录架构详解
  • 从零构建鸿蒙应用:深度解析应用架构与项目结构
  • linux 内核: 遍历当前所有进程
  • AR眼镜:重塑医学教育,开启智能教学新时代
  • 图像修复:深度学习实现老照片划痕修复+老照片上色
  • 物联网系统中MQTT设备数据的保存方法
  • HC595串转并
  • CUDA 环境下 `libcuda.so` 缺失问题解决方案
  • linux网络编程之单reactor模型(二)
  • 僵尸进程Zombie Process
  • Java核心类库深度解析与实战:从字符串处理到计算器开发
  • 【Android】按钮的使用
  • Windows远程FX的编解码器性能优化
  • vscode 打开c++文件注释乱码
  • WPF,Winform,HTML5网页,哪个UI开发速度最快?
  • 智驾芯片软件分层测试
  • Element plus参考vben逻辑实现的描述列表组件封装实践
  • Spark Expression codegen
  • 利用DeepSeek为chdb命令行客户端添加输出重定向和执行SQL脚本功能