【Linux】线程控制
在Linux操作系统内部,不管是进程还是线程。它以PCB为准,只有在用户态里才有线程的概念。一般实现线程会用到一个POSIX线程库,在这里可以通过调用POSIX库里的函数来实现有关线程的各种操作。不过内核中也有一种内核级线程。
用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。如POSIX线程库。
系统级线程(核心级线程):由操作系统内核进行管理。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行、撤消线程。
POSIX线程库:
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以 “pthread_” 打头的要使用这些函数库,要通过引入头文 <pthread.h>链接这些线程函数库时要使用编译器命令的 “-lpthread” 选项
一、创建线程
1 .线程创建函数
与fork父子进程类似,创建出新进程后,新线程去执行划分给他的函数。主线程继续往下执行自己的代码。这里不分先后的执行。
#include <pthread.h>
int pthread_create(
pthread_t *thread, /* 线程id,将线程id存入,线程标识符,线程栈的起始地址,输出型参数,用以获取创建成功的线程 ID */
const pthread_attr_t *attr,/* 设置创建线程的属性,使用 nullptr 表示使用默认属性 */
void *(*start_routine) (void *), /* 函数地址,该线程启动后需要去执行的函数 */
void *arg);/* 线程要去执行的函数的形参,没参数时可填写 nullptr */
//返回值:成功返回0,失败返回错误码,如:
//EAGAIN 描述: 超出了系统限制,如创建的线程太多。
//EINVAL 描述: tattr 的值无效。
错误检查:
传统的一些函数是,成功返回 0 ,失败返回 -1 ,并且对全局变量 errno 赋值以指示错误。pthreads 函数出错时不会设置全局变量 errno (而大部分其他 POSIX 函数会这样做)。而是将错误代码通过返回值返回pthreads 同样也提供了线程内的 errno 变量,以支持其它使用 errno 的代码。对于 pthreads 函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno 变量的开销更小
例:
// 新线程
void* fun(void* args)
{
while (true)
{
std::cout << "新线程正在运行" << std::endl;
sleep(1);
}
}
// 主线程
int main()
{
// 记录线程 id
pthread_t tid;
// 创建新线程,并让新线程去执行 fun 函数
if(pthread_create(&tid, nullptr, fun, nullptr)!=0)
{
perror("pthread_create:");
exit(-1);
}
// 主线程继续执行自己后续的代码
while (true)
{
std::cout << "主线程正在运行" << std::endl;
sleep(1);
}
return 0;
}
执行程序:主新进程各自自行,互不干涉
我们可以打开查看进程的监控和查看轻量级进程的监控:
可以发现,进程只有一个。线程有两个,其中一个线程的LWP与PID一样,这是主进程。比主进程LWP大1的是新进程。
解释:
- LWP (Light Weight Process) 表示的就是线程 (轻量级进程) 的 ID,其中主线程的 LWP 值等同于进程的 PID 值。
2 .线程传参
pthread_create函数创建出来的线程所执行的函数的形参类型是 void* ,可以传递任何类型的参数,当然也能传对象给线程使用。
例:这里我将新线程命名传给新进程,新进程执行打印自己名字的函数
struct Data
{
Data(const std::string &name): _name(name)
{}
std::string GetName()
{
return _name;
}
std::string _name;
};
void *fun(void *args)
{
Data *td = static_cast<Data *>(args);
while (true)
{
std::cout << "新线程 " << td->GetName() << " 正在运行" << std::endl;
sleep(1);
}
}
int main()
{
Data *d = new Data("thread 1");
// 创建新线程,将d对象作为fun函数的参数
pthread_t tid;
pthread_create(&tid, nullptr, fun, d);
while (true)
{
std::cout << "主线程正在运行" << std::endl;
sleep(1);
}
return 0;
}
执行结果:
3 . 线程ID及进程地址空间布局
问题:tid 是什么样子的——虚拟地址
pthread_t pthread_self ( void );
可以看见,每个线程有自己独立的栈和空间和数据。
pthread_create创建进程函数会将 tid 修改为现场控制块的地址。 未来我们只需要找到线程控制块的地址即可;
4. 创建多线程
- 每个线程都由各自的线程 ID 所管理着,创建多线程时可以使用一个数据结构将每个创建出来的线程的 ID 管理起来。
例:
struct Data
{
Data(const std::string &name)
: _name(name)
{}
std::string thread_name()
{
return _name;
}
std::string _name;
};
void *thread_run(void *args)
{
Data *td = static_cast<Data *>(args);
while (true)
{
std::cout << "新线程 " << td->thread_name() << " 正在运行" << std::endl;
sleep(1);
}
return nullptr;
}
const int gnum = 5; // 5 个线程
int main()
{
// 存储创建的所有线程的线程 ID
std::vector<pthread_t> tids(gnum);
// 创建 5 个线程并给每个线程传递一个 Data 对象,都执行打印自己名字的函数
for (size_t i = 0; i < gnum; i++)
{
std::string name = "thread " + std::to_string(i + 1);
Data *td = new Data(name);
pthread_create(&tids[i], nullptr, thread_run, td);
}
while (true)
{
std::cout << "主线程正在运行" << std::endl;
sleep(1);
}
return 0;
}
执行结果:
使用
ps -aL
指令打开监控可以查到主和新线程这 6 个线程的情况。
二、线程终止
如果只是想终止某个线程而不是整个进程,可以有如下 3 种方法
1. 从线程函数 return 。这种方法对主线程不适用 , 从 main 函数 return 相当于调用 exit 。2. 线程可以调用 pthread_ exit 终止自己。3. 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程。
1 .使用 return 终止线程
- return的作用是退出函数。在线程调用的函数中使用 return 退出当前线程(执行完毕,没有代码执行了,就退出了),但是在main函数中使用return代表整个进程退出(main函数执行完毕),也就是说只要主线程退出了那么整个进程就退出了,此时该进程曾经申请的资源就会被释放,而其他线程会因为没有了资源,自然而然的也退出了。
2 .使用 pthread_exit 函数终止线程
pthread_exit 是 POSIX 线程(也称为 pthreads)库中的一个函数,用于显式地终止调用它的线程。当线程通过 pthread_exit
函数退出时,它可以返回一个指向某个对象的指针,这个对象可以是任何类型的指针。需要注意的是,返回的指针不会被自动释放,因此接收者(如通过 pthread_join
获取退出状态的线程)需要负责适当地处理这个指针(例如,释放分配的内存)。
因此:函数返回的指针所指向的内存单元必须是全局的或者是用malloc (new)分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了。
#include <pthread.h>
void pthread_exit(void *retval);
//retval:这是一个指向任意类型的指针,通常用于表示线程的退出状态或返回值。如果不需要返回值,可以传递 NULL。
注意:
- 线程一旦调用
pthread_exit
,就会立即终止执行,其调用之后的任何代码都不会被执行。- 如果线程通过
pthread_exit
退出,那么该线程应占用的资源(如栈空间)将由 pthread 库自动回收。- 使用
pthread_exit
退出的线程不会释放它通过malloc
、new
等方式分配的内存。这些内存必须在线程退出前由程序员显式释放。- 线程可以通过
pthread_join
函数等待另一个线程的终止,并获取该线程的返回值(通过pthread_exit
传递的retval
参数)。
示例:
void* thread_function(void* arg) {
// 线程的工作内容
printf("Thread is running\n");
// 假设我们在这里完成了工作,现在准备退出
// 返回一个指向整数的指针,表示退出状态
int* status = (int*)malloc(sizeof(int));
*status = 0; // 假设 0 表示成功 当然也可以传入其他类型指针,强转就行了
pthread_exit((void*)status);
// 以下代码不会被执行,因为线程已经通过 pthread_exit 退出了
printf("This line will not be executed\n");
// free(status);
}
int main() {
pthread_t thread;
// 创建线程
pthread_create(&thread, NULL, thread_function, NULL);
int* retval=nullptr;
// 等待线程结束,并获取其返回值
pthread_join(thread, (void**)&retval);
// 处理线程的返回值
if (thread_return != NULL) {
printf("Thread returned: %d\n", *thread_return);
free(thread_return); // 释放之前线程函数分配的内存
}
return 0;
}
执行结果:
3 . 使用 pthread_cancel 函数终止线程
pthread_cancel 是 POSIX 线程(pthread)库中的一个函数,用于向指定的线程发送取消请求,请求该线程终止执行。这个函数的行为和效果受到多个因素的影响,包括线程对取消请求的反应状态、取消类型的设置以及线程中取消点的存在与否。
#include <pthread.h>
int pthread_cancel(pthread_t thread);
//thread:指定要取消的线程的标识符(ID)。
返回值:
- 成功时返回
0
。- 失败时返回错误码。
取消请求的处理
取消点:线程在接收到取消请求后,并不会立即终止。它将继续执行,直到到达一个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。这些取消点通常是特定的系统调用或库函数。与信号有一点类似,在特定的点进行检测执行。
注意:
pthread_cancel
调用本身并不等待线程终止,它只是发送了一个取消请求。如果需要确保线程已经终止,可以使用pthread_join
函数。- 线程在无限循环或没有自然取消点的情况下,可能无法响应取消请求。在这种情况下,可以通过在循环体中添加
pthread_testcancel
调用来手动创建取消点。- 取消一个线程可能会导致资源释放的问题,特别是如果线程在持有锁或其他资源时被取消。因此,在编写多线程程序时,需要仔细考虑如何安全地释放这些资源。
例如:新线程执行5秒后被主线程取消
void* thread_run(void* args)
{
std::string thread_name = static_cast<const char*>(args);
while (true)
{
std::cout << thread_name << " 正在运行" << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, thread_run, (void*)"thread -1");
std::cout << "主线程正在运行" << std::endl;
sleep(5);
pthread_cancel(tid); // 主线程取消新线程
std::cout << "新线程已被终止" << std::endl;
return 0;
}
执行结果:
三、等待线程
问题:为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。
1 .线程等待函数
- 该函数在等待成功时会返回 0,失败时返回对应的错误码。
#include <pthread.h>
int pthread_join(
pthread_t thread,/* 被等待的线程的线程 ID */
void **retval);/* 获取被等待的线程在退出时的返回值 */
- 调用该函数的线程将阻塞等待新线程,直到所等待的的新线程终止为止,被等待的线程以不同的方式终止时,通过 pthread_join 得到的终止状态也是不同的。
2 .获取返回值
- pthread_create 创建出的线程所调用函数的返回值是一个 void* 类型的值,而 pthread_join 函数的第二个输出型参数是 void** 类型,就是为了获取新线程所调用函数的返回值。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
// 线程运行的函数
void* threadFunction(void* arg) {
// 假设我们不需要传递任何参数给线程函数
// 这里的arg参数只是为了满足pthread_create的签名
(void)arg;
// 动态分配一个字符串
char* message = strdup("Hello from thread!");
if (message == NULL) {
// 如果内存分配失败,则线程返回NULL
return NULL;
}
// 线程返回字符串的地址
return (void*)message;
}
int main() {
pthread_t threadId;
void* threadResult;
char* message;
// 创建一个新线程
if (pthread_create(&threadId, NULL, threadFunction, NULL) != 0) {
printf("Error: unable to create thread\n");
return 1;
}
// 等待线程结束并获取其返回值
if (pthread_join(threadId, &threadResult) != 0) {
printf("Error: unable to join thread\n");
return 1;
}
// 将void*类型的返回值转换为char*类型
message = (char*)threadResult;
// 打印字符串
printf("%s\n", message);
// 释放字符串以避免内存泄漏
free(message);
return 0;
}
执行结果:
我们也可以返回自定义类型(对象)。这可以让我们得到线程执行任务结束后给我们返回执行的结果。但是要记住释放返回的内存。
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 参数。
四、分离线程
1 .线程分离概念
一般情况下,主线程是需要阻塞等待新线程的,如果不等待线程退出,就有可能造成类似 “僵尸” 问题。
但如果主线程就是不想等待新线程退出,也不关心新线程的返回值,此时就可以将新线程分离,被分离的线程在退出时会自动释放资源。类似信号设置为忽略一样。
分离出的新线程依旧要使用进程的资源,且依旧在该进程内运行,甚至这个新线程崩溃了也会影响整个进程。线程分离只是让主线程不需要再阻塞等待整个新线程罢了,在新线程退出时系统会自动回收该线程所持有的资源。
一个线程可以将其他线程分离出去,也可以将自己分离出去。
pthread_detach(pthread_self());
等待和分离会发生冲突,一个线程不能既是可被等待的又是分离出去的。
虽然分离出去的线程已经不归主线程管了,但一般还是建议让主线程最后再退出。
分离出去的线程可以被 pthread_cancel 函数终止,但不能被 pthread_join 函数等待。
2 .线程分离函数
#include <pthread.h>
int pthread_detach(pthread_t thread); // thread 是要分离出去的线程的 ID
//线程分离成功时返回 0,失败时则返回对应错误码。
例:
void* thread_function(void* arg) {
// 线程执行的代码
printf("Thread is running\n");
return nullptr;
}
int main() {
pthread_t thread_id;
int result;
// 创建线程
result = pthread_create(&thread_id, NULL, thread_function, NULL);
if (result != 0) {
perror("Failed to create thread");
exit(EXIT_FAILURE);
}
// 将线程设置为分离状态
result = pthread_detach(thread_id);
if (result != 0) {
perror("Failed to detach thread");
exit(EXIT_FAILURE);
}
sleep(3);
int n=pthread_join(thread_id,nullptr);
std::cout<<"pthread_join return val : "<<n<<std::endl;
return 0;
}
执行结果:
可以看到,线程分离后pthread_join的返回值是22,说明等待出错。
原文地址:https://blog.csdn.net/2301_77438812/article/details/142376657
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!