🌻个人主页:路飞雪吖~
🌠专栏:Linux
目录
一、Linux线程控制
✨POSIX线程库
• 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"打头的;
• 要使用这些函数库,要通过引入头文件<pthread.h>;
• 链接这些线程函数库时要使用编译器命令的 "-lpthread" 选项;
✨创建线程
在Linux内核中,没有线程的概念【没有单独设计tcb,只有进程pcb】!只有LWP,轻量级进程的概念,线程是使用LWP模拟的!这就意味着,Linux操作系统,不会给我们提供线程接口,只会提供创建轻量级进程的接口!
cpp
功能:创建⼀个新的线程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数:
thread:返回线程ID
attr:设置线程的属性,attr为NULL表⽰使⽤默认属性
start_routine:是个函数地址,线程启动后要执⾏的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
错误检查:
• 传统的一些函数,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误;
• pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)而是将错误代码通过返回值返回;
• pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小;

为了服务上层用户,在操作系统和用户之间封装一层软件层 【计算机当中任何问题,都可以新增一层软件层来完成】,封装一层软件层:把 clone() 系统调用封装成线程创建的接口,此时就可以使用库,库来完成所有线程的创建未来的管理,此时用户就不用关系 操作系统对于LWP的概念,只需要用线程相关的概念来进行上层代码的编写---- 用户级线程。
Window有真正的TCB,就能提供系统级别的进程和线程的创建接口。
<pthread.h> 用户级别的线程库【独立的库】,Linux系统自带的!原生线程库!
bash
// Makefile
mythread:mythread.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f mythread
cpp
#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库!
cpp
//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"来唯一标识这个线程。
cpp
#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库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。
🌠一个进程内有多个执行流,多个线程执行同一个函数:这个函数被重入了!!
cpp
#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. 全局变量在线程内部是共享的;
cpp
#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会给每一个线程 设置异常处理。【信号是给进程的,进程中的每一个线程都会收到信号】
cpp
#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 得到的终止状态是不同的,总结如下:
如果thread线程通过return返回,value_ptr 所指向的单元里存放的是thread线程函数的返回值。
如果thread线程被别的线程调用 pthread_cancel 异常终掉, value_ptr 所指向的单元里存放的是常数 PTHREAD_CANCELED【宏】。
如果thread线程是自己调用 pthread_exit 终止的, value_ptr 所指向的单元存放的是传给 pthread_exit 的参数。
如果对thread线程的终止状态不感兴趣,可以传NULL给 value_ptr参数。
cpp
#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:线程等待成功!
cpp
#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。
cpp
#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,而是自己本身的ID
if(n != 0)
{
std::cout << "join error:" << n << "," << strerror(n) << std::endl;
return 1;
}
std::cout << "join success!" << std::endl;
}

pthread_join这个线程若一直不退,主线程就会一直阻塞等待。
pthread_join默认是阻塞式的,让主线程阻塞时等待。
8. 线程传参问题:传递参数,可以是变量、数字、对象、结构体、类....
cpp
#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,而是自己本身的ID
if (n != 0)
{
std::cout << "join error:" << n << "," << strerror(n) << std::endl;
return 1;
}
std::cout << "join success!" << std::endl;
}

🍔小贴士:
cpp#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,而是自己本身的ID if (n != 0) { std::cout << "join error:" << n << "," << strerror(n) << std::endl; return 1; } std::cout << "join success!, ret: " << (long long int)ret << std::endl; }
理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!
• 传参问题:传递参数,可以是变量、数字、对象
• 返回值问题:返回参数,可以是变量、数字、对象
cpp#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,而是自己本身的ID if (n != 0) { std::cout << "join error:" << n << "," << strerror(n) << std::endl; return 1; } std::cout << "join success!, ret: " << rtd->Result() << std::endl; delete td; }
🌠创建多线程与等待,如何把任务进行处理:
cpp#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 10 int 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()); } }
在多线程当中所有的东西都是共享的,线程有独立的栈,所以线程里面的局部变量,不能被其他线程看到;但是想要被看到也是有方法的。
在线程的代码里,不同线程定义的临时变量,是在不同的栈上的。
cpp#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分配的, 不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
cpp
#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() ,线程退出 若不等待,就会变成类似僵尸进程的问题。
cpp
#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是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
cpp
#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*()。

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