作者: 主页
我的专栏 C语言从0到1 探秘C++ 数据结构从0到1 探秘Linux 菜鸟刷题集 欢迎关注:点赞收藏✍️留言
码字不易,你的点赞收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!
随着计算机技术的不断发展,多线程编程已经成为了程序设计中的一种重要方式。在Linux系统中,线程控制是多线程编程的核心内容之一。线程是一种轻量级的执行单元,它能够提高程序的并发性和响应速度,同时也能够有效地利用系统资源。
在Linux系统中,线程的控制主要涉及到线程的创建、等待、唤醒和销毁等方面。本文将详细介绍Linux中的线程控制机制,包括线程的状态转换、线程间的同步与互斥、线程的优先级以及线程的调度等方面。通过本文的学习,读者可以深入理解Linux中的线程控制原理,掌握线程编程的基本技巧和方法,为编写高效稳定的多线程程序打下坚实的基础。
这里需要用到之前讲过的线程创建的函数 pthread_create
函数
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
返回值与参数说明:
代码演示:
下面代码的作用是创建一个子线程,这个线程不停的打印参数发过去的消息,同时我们的主线程不停的打印另外的消息 在子线程中还调用了一个pthread_self()
函数 它的作用是返回当前线程的id
#include
#include
#include
#include
void* run_thread(void* args)
{
char* msg = (char*)args;
while(1)
{
printf("I'm a new pthread my tid is: %lu\n" , pthread_self());
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid ,NULL ,run_thread ,(void*)"thread 1");
while(1)
{
printf("im main thread i create the new pid is:%lu\n", tid);
sleep(1);
}
return 0;
}
运行结果可能出现下面的情况:
这是因为程序找不到库文件导致的,我需要在编译的命令后面加上-l pthread
这样就可以让它正常运行了
结果如下:
可以观察到主线程创建的子线程的pid就是新线程的pid
并且做到之前从没做到过的事
两个死循环
但是线程的健壮性不够强,只要有一个线程崩溃它就都会崩溃
演示一下,让新线程运行一段时间后出现一个野指针问题 观察实验现象
演示代码如下:
#include
#include
#include
#include
void* run_thread(void* args)
{
char* msg = (char*)args;
while(1)
{
printf("im a new pthread my tid is: %lu\n" , pthread_self());
sleep(3);
int* p = NULL;
*p = 20;
}
}
int main()
{
pthread_t tid;
pthread_create(&tid ,NULL ,run_thread ,(void*)"thread 1");
while(1)
{
printf("im main thread i create the new pid is:%lu\n", tid);
sleep(1);
}
return 0;
}
在学习进程控制的时候讲过进程根据需要会进行进程等待 不然可能会造成僵尸进程的问题 而线程同样是通过PCB来保存数据的 所以线程也需要进行线程等待
在Linux操作系统中 我们可以使用pthread_join
函数来实现
int pthread_join(pthread_t thread, void **retval);
返回值和参数说明:
调用该函数的线程将被挂起等待直到ID为thread的线程终止
演示代码:
下面的代码的行为是:先创建一个新线程 这个线程会打印自己的线程id 之后休眠3秒并结束
主线程使用pthread_join
函数接收它的返回值并打印
这里要注意的是:我们不能直接打印status 而是要将它强制转换为intptr_t
再强制转换为int
#include
#include
#include
#include
void* run_thread(void* args)
{
printf("im new thread my tid is:%lu\n", pthread_self());
sleep(3);
return (void*)101;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, run_thread, (void*)"thread 1");
void* status = NULL;
printf("waiting ...\n");
pthread_join(tid, &status);
printf("wait success, exit code is:%d\n", (int)(intptr_t)status);
return 0;
}
结果如下:
可以看到主线程收到了新线程的退出码101
也就知道了pthread_join
函数确实会进行线程等待 而调用这个函数的线程要等到新线程结束才继续执行
如果有异常情况需要处理吗?
答案是不需要 因为当一个线程出现异常情况的时候 操作系统就会发信号给进程 从而杀死进程
这里还要注意的当我们创建了多个线程时 只能一个个等待
在前面线程等待中看到 线程结束用的是return
return 包含两种情况
线程退出除了使用return以外 还有专门的函数pthread_exit
函数
void pthread_exit(void *retval);
返回值和参数说明:
演示代码:
下面代码使用这个函数来退出线程 返回值是520
#include
#include
#include
#include
void* run_thread(void* args)
{
printf("im new thread my tid is:%lu\n", pthread_self());
sleep(3);
pthread_exit((void*)520);
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, run_thread, (void*)"thread 1");
void* status = NULL;
printf("waiting ...\n");
pthread_join(tid, &status);
printf("wait success, exit code is:%d\n", (int)(intptr_t)status);
return 0;
}
int pthread_cancel(pthread_t thread);
返回值和参数说明:
演示代码:
下面代码的作用是创建线程以后马上取消并且查看退出码
#include
#include
#include
#include
void* run_thread(void* args)
{
printf("im new thread my tid is:%lu\n", pthread_self());
while(1)
{
// 检查取消状态
if (pthread_cancel(pthread_self()) != 0) {
printf("Thread is being cancelled\n");
pthread_exit((void*)-1);
}
sleep(3);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, run_thread, (void*)"thread 1");
void* status = NULL;
printf("waiting ...\n");
sleep(5); // 等待5秒钟
pthread_cancel(tid); // 发送取消请求
pthread_join(tid, &status);
if (status == PTHREAD_CANCELED) {
printf("Thread was successfully cancelled\n");
} else {
printf("Thread was not cancelled\n");
}
return 0;
}
结果如下:
PTHREAD_CANCELED是什么?
其实它就是 -1 只不过它是宏定义而已 所以用他来检查退出是否正常
查看一下在系统中是怎么定义的:
可以看到它实际上是被定义为**((void*)-1)**
需要注意的是默认创建线程的时候 线程的属性是joinable属性 它会导致线程退出的时候需要别人来回收自己的退出资源 线程退出了 但是线程在共享区中的空间还没有释放
什么是线程分离?
一个线程如果被设置为分离属性 那这个线程在退出后 不需要其他执行流来回收该资源 而是由操作系统进行回收 它既可以是被线程组内的其他线程分离 又可以是线程自己分离 就和线程取消一样 当这个线程被设置为线程分离 就不能被等待了 这是相冲突的 它分离后就是"孤家寡人"了
我们可以使用pthread_detach
函数来进行线程分离
int pthread_detach(pthread_t thread);
虽然前面说它分离后就变成“孤家寡人” 但是如果这个线程崩溃之后 进程同样会崩溃 那线程分离的意义在哪里?
线程分离的主要目的在于允许线程独立于创建它的进程运行,从而避免资源泄漏和提高系统性能。虽然线程分离后确实会变成“孤家寡人”,不再受到主线程的管辖,但是这也意味着对于主线程来说,无需等待这些“孤家寡人”线程的结束,从而能够更快地继续执行自己的任务。
当一个线程被分离后,线程终止时它所占用的系统资源会被自动释放,无需其他线程或者主线程专门等待或者回收资源,进而降低了系统负担。此外,线程分离还可以简化线程的管理,尤其是在多线程程序中,当有大量的线程需要创建时,分离线程可以减少对线程的管理和资源回收的工作量。
演示代码:
在创建完这个线程之后就分离它 并在三秒之后终止主线程和分离的线程
#include
#include
#include
#include
void* run_thread(void* args)
{
printf("im new thread my tid is:%lu\n",pthread_self());
printf("hello im new thread\n");
sleep(3);
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, run_thread, (void*)"thread 1");
printf("waiting ...\n");
pthread_detach(tid);
sleep(3);
return 0;
}
演示代码:
让主线程打印自己的进程ID、线程ID和新线程的ID 让新线程打印自己的进程ID和线程ID
#include
#include
using namespace std;
#include
#include
void* thread_run(void* arg)
{
while(1)
{
cout<<"i am:"<<(char*)arg<<"pid:"<<getpid()<<" "<<"my thread id is:"<<pthread_self()<<endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
int ret=pthread_create(&tid,NULL,thread_run,(void*)"thread 1");
if(ret!=0)
{
return -1;
}
while(1)
{
cout<<"i am main thread id:"<<pthread_self()<<" "<<"new thread:"<<tid<<" "<<"pid:"<<getpid()<<endl;
sleep(2);
}
return 0;
}
结果如下:
线程ID 本质就是一个进程地址空间上的一个地址 也就是虚拟地址
我们可以通过ldd
命令查看一下编译好的文件
先介绍一下ldd
ldd
是一个在 Linux 系统中用于显示共享库依赖关系的命令。它列出了程序运行时所需的动态链接库及其路径。使用方法如下:
ldd [选项] 可执行文件或库
例如,要查看名为 code
的可执行文件的依赖关系,可以使用以下命令:
ldd code
我们所链接的线程库实际上就是一个动态库 既然是库肯定就是一个文件
当这个文件被加载到物理内存之后会经过页表的映射加载到进程地址空间的共享区中
我们之前说每个进程都有自己的栈空间 实际上这个栈空间和我们所想的是不一样的
线程采用的栈是在共享区开辟的 除此以外线程还有自己的struct pthread 当中包含了对应线程的各种属性
每个线程还有自己的线程局部存储 私有数据 还有线程被切换时的上下文数据
每当我们增加一个线程 在共享区中就会增加一个对应的结构体
在上面我们所用的各种线程函数 它的本质就是在库内部对线程属性进行各种操作 最后要执行的代码交给对应的内核级线程(轻量级进程)去执行即可 可以说对线程的管理就是基于共享区的
而这些个LWP就存在一个个结构体中
我们所看到的tid就是一个个结构体的虚拟地址
pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。