Linux操作系统6- 线程3(线程的取消,分离与独立存储)
上篇文章:Linux操作系统6- 线程2(线程的创建,终止,等待与退出)-CSDN博客
本篇Gitee仓库:myLerningCode/l28 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)
目录
一. 线程取消pthread_cancle
二. 线程分离pthread_detach⭐
2.1 主线程join时发生detach
2.2 正确的调用线程分离
三. Linux线程栈与独立存储
3.1 线程的id
一. 线程取消pthread_cancle
线程除了退出和终止,还能取消某一个线程。
//所需头文件
#include <pthread.h>
//用于取消thread这个线程
int pthread_cancle(pthread_t thread);
//注意,调用pthread_cancle之后该线程不会立即取消,而是在某些取消点取消
//(比如 sleep read write pthread_cond_wait)
//我们也能使用下面的调用来显示触发
void pthread_testcancel(void); // 显式触发取消检查
测试代码如下:
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;
const int NUM = 10;
class ThreadData
{
public:
int number;
pthread_t tid;
char namebuffer[64];
};
class ThreadReturn
{
public:
int exit_code;
int exit_result;
};
void *start_routine(void *arg) // 这个函数被多个线程进入,是可重入函数
{
sleep(1);
ThreadData *td = static_cast<ThreadData *>(arg);
int cnt = 10;
while (cnt)
{
cout << "new thread name:" << td->namebuffer << " cnt: " << cnt-- << " cnt地址:" << &cnt << endl;
sleep(1);
}
ThreadReturn *tr = new ThreadReturn();
tr->exit_code = 0;
tr->exit_result = td->number;
return (void *)1; // 右值
}
int main()
{
// 1.创建一批线程
vector<ThreadData *> threads; // 用于线程保存线程的数据
for (int i = 0; i < NUM; i++)
{
ThreadData *td = new ThreadData();
td->number = i;
snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i + 1);
pthread_create(&td->tid, NULL, start_routine, td);
threads.push_back(td);
}
for (auto &thread : threads)
{
cout << "creat thread: " << thread->namebuffer << ":" << thread->tid << " success" << endl;
}
sleep(5);
// 线程取消,这里取消前5个线程
for (int i = 0; i < threads.size() / 2; i++)
{
pthread_cancel(threads[i]->tid);
cout << "cancel thread: " << threads[i]->namebuffer << " success" << endl;
}
// 创建时候循环创建,等待的时候一次性等待
for (auto &thread : threads)
{
void *ret = nullptr;
int n = pthread_join(thread->tid, &ret); // void **retp, *retp = ret , *ret = 值
assert(n == 0);
(void)n;
cout << "join " << thread->namebuffer << " success exitcode:" << (long long)ret << endl;
delete thread;
}
cout << "main thread quit" << endl;
return 0;
}
在创建线程然后主线程休眠5秒后取消5个线程,测试结果如下:
可以看到,线程取消后,如果调用了join会等待线程退出 。不过我们不建议使用取消线程,直接使用线程退出即可。
pthread_exit是线程自己运行完之后结束自己
pthread_cancle一般用于外部结束某一个线程
二. 线程分离pthread_detach⭐
使用pthread_join可以等待线程退出的信息。但是这种等待是阻塞式等待,如果主线程不想等待线程而继续执行其他代码呢?这时候就需要使用线程分离。
我们创建进程之后,可以调用wait/waitpid来等待子进程退出,如果主进程想要不关心子进程的退出信息,而继续执行自己的代码。可以将子进程的退出信号设置为SIG_IGN。
与进程退出一样,线程退出也有相似的操作来让主线程不用等待新线程。这种操作就是线程分离
//所需头文件
#include <pthread.h>
int pthread_detach(pthread_t thread)
//调用该函数后,会分离thread这个线程
2.1 主线程join时发生detach
测试代码如下:
在下面这份代码中,新线程通过pthread_self自己分离自己,同时主线程调用pthread_join来等待新线程退出,看看会发生什么结果。
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
std::string changeId(const pthread_t &thread_id)
{
char tid[128];
snprintf(tid, sizeof(tid), "0x%x", thread_id);
return tid;
}
void *start_routine(void *args)
{
std::string name = static_cast<const char *>(args);
sleep(1); //让主线程先join,新线程再去分离
pthread_detach(pthread_self()); // 线程将自己设置为分离
int cnt = 5;
while (cnt--)
{
std::cout << name << " is running! " << changeId(pthread_self()) << std::endl;
sleep(1);
}
reutnr nullptr
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, start_routine, (void *)"thread 1");
//sleep(1);
std::string maintid = changeId(pthread_self());
std::cout << "main tid:" << maintid << " newthread tid:" << changeId(tid) << std::endl;
// 一个线程默认是joinable的,如果设置了分离,不会等待线程
int n = pthread_join(tid, NULL);
std::cout << "result " << n << ":" << strerror(n) << std::endl;
return 0;
}
运行结果:
可以看到,异常退出了。
如果主线程正在join新线程的时候,这个线程调用了detach分离自己,那么就会发生这种错误。主线程join会立刻出错返回!
2.2 正确的调用线程分离
为了线程的正常分离,我们一般在主线程中去分离想要分离的线程,且分离某一个线程之后就不要继续等待这个线程了,否则会出错返回。
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
std::string changeId(const pthread_t &thread_id)
{
char tid[128];
snprintf(tid, sizeof(tid), "0x%x", thread_id);
return tid;
}
void *start_routine(void *args)
{
std::string name = static_cast<const char *>(args);
int cnt = 5;
while (cnt--)
{
std::cout << name << " is running! " << changeId(pthread_self()) << std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, start_routine, (void *)"thread 1");
pthread_detach(tid); // 主线程分离新线程
std::string maintid = changeId(pthread_self());
while (true)
{
std::cout << "main tid:" << maintid << " newthread tid:" << changeId(tid) << std::endl;
sleep(1);
}
// 此时就不要等待新线程了!
return 0;
}
运行结果如下:
三. Linux线程栈与独立存储
在Linux中,使用线程必须要包含pthread.h线程库。这意味着在Linux中:
轻量级进程:用户级线程 = 1 : 1。
用户关系的线程属性在库中,而线程的调度由cpu完成
3.1 线程的id
我们使用pthread_create获取的id其实是:线程的其实地址,且这个地址位于虚拟空间的堆栈之间的共享区域。(准确的说是mmap区域)
证明如下:
#include <iostream>
#include <string>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
void *start_routine(void *args)
{
std::string name = static_cast<const char *>(args);
sleep(10000);
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, start_routine, (void *)"thread 1");
pthread_detach(tid); // 主线程分离新线程
int a = 10;
int *b = new int;
while (true)
{
printf("栈地址:%p\n", &a);
printf("线程tid:%p\n", tid);
printf("堆地址:%p\n", b);
sleep(1);
}
// 此时就不要等待新线程了!
return 0;
}
测试结果如下:
虚拟地址空间的位置如下: