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

【Linux】线程概念与控制(2)

3. Linux 线程控制:POSIX 线程库的核心操作

Linux 中通过 **POSIX 线程库(pthread)** 实现线程控制,包括线程创建、终止、等待、分离等操作。这些操作是多线程编程的基础,本节详解其用法与原理。

3-1 POSIX 线程库基础

POSIX 线程库是 Linux 下多线程编程的标准接口,核心特点:

  • 函数命名:线程相关函数以pthread_开头(如pthread_createpthread_join)。
  • 头文件:需包含<pthread.h>
  • 编译链接:pthread是第三方库,所以编译时需加-lpthread选项(链接 pthread 库)。

3-2 创建线程:pthread_create

功能

创建新线程,使其执行指定函数。

函数原型

int pthread_create(pthread_t *thread,         // 输出参数:返回线程IDconst pthread_attr_t *attr, // 线程属性(NULL表示默认属性)void *(*start_routine)(void*), // 线程执行的函数(函数指针)void *arg                   // 传递给线程函数的参数
);

返回值

  • 成功:返回0
  • 失败:返回错误码(非-1,pthread 函数不设置errno)。

示例

#include<iostream>
#include<pthread.h>
#include<unistd.h>void* threadrun(void* args)
{std::string name = (const char*)args;while(1){std::cout << "我是新线程" << name << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;int n = pthread_create(&tid, nullptr, threadrun, (void*)"thread-1");if(n != 0){std::cout << "pthread_create error" << std::endl;return 1;}while(1){std::cout << "我是主线程" << std::endl;sleep(1);}return 0;
}

  • 内核线程 ID(LWP):通过ps -aL查看,是内核调度的唯一标识(系统全局唯一)。

CPU调度的时候,看得实际是lwp。

3-3 线程终止

线程可通过以下 3 种方式终止,且仅终止自身(不影响进程内其他线程):

1. 线程函数return

线程函数执行到return时,线程自然终止(主线程return会导致进程退出)。

2. pthread_exit主动终止

void pthread_exit(void *value_ptr);
  • value_ptr:线程退出时的返回值(需为全局 / 堆内存,不能是栈变量)。
  • 无返回值(线程自身无法 “返回” 到调用者)。

3. pthread_cancel取消其他线程

int pthread_cancel(pthread_t thread);
  • 向目标线程发送 “取消请求”,目标线程在取消点(如sleepread等系统调用)响应并终止。
  • 返回值:成功0,失败错误码。

3-4 线程等待:pthread_join

在 POSIX 线程(pthread)中,若创建的非分离态线程未通过 pthread_join 等待回收,会导致僵尸线程,引发以下问题:

  1. 资源泄漏:线程结束后,内核中的线程控制块(TCB)不会释放,持续占用内核内存和线程描述符等稀缺资源。大量僵尸线程会耗尽系统线程资源,导致无法创建新线程。

  2. 内存泄漏:线程分配的私有资源(如堆内存、文件描述符)若依赖 pthread_join 回收(例如通过线程返回值传递资源指针),不调用该函数会导致这些资源无法释放,造成用户态内存泄漏。

  3. 程序逻辑隐患pthread_join 可获取线程退出状态,不调用则主线程无法得知子线程是否正常结束、是否返回预期结果,可能引发逻辑错误。

功能

等待指定线程终止,回收其资源(类似进程的wait)。

函数原型

int pthread_join(pthread_t thread,   // 要等待的线程IDvoid **value_ptr    // 输出参数:存储线程的退出状态
);

线程退出状态的获取

  • 若线程通过return退出:value_ptr存储线程函数的返回值。
  • 若线程被pthread_cancel终止:value_ptr存储PTHREAD_CANCELED
  • 若线程调用pthread_exitvalue_ptr存储pthread_exit的参数。

示例

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>void *thread1(void *arg) {int *ret = malloc(sizeof(int));*ret = 100;return (void *)ret; // return退出,返回值存于堆
}void *thread2(void *arg) {int *ret = malloc(sizeof(int));*ret = 200;pthread_exit((void *)ret); // pthread_exit退出
}void *thread3(void *arg) {while (1) sleep(1); // 无限循环,等待被取消
}int main() {pthread_t tid;void *ret;// 测试return退出pthread_create(&tid, NULL, thread1, NULL);pthread_join(tid, &ret);printf("Thread 1 return: %d\n", *(int *)ret);free(ret);// 测试pthread_exit退出pthread_create(&tid, NULL, thread2, NULL);pthread_join(tid, &ret);printf("Thread 2 exit: %d\n", *(int *)ret);free(ret);// 测试pthread_cancel退出pthread_create(&tid, NULL, thread3, NULL);sleep(1);pthread_cancel(tid);pthread_join(tid, &ret);if (ret == PTHREAD_CANCELED)printf("Thread 3 canceled\n");return 0;
}

3-5 分离线程:pthread_detach

背景

默认情况下,线程是joinable的:若不调用pthread_join,线程终止后资源不会自动释放,导致 “僵尸线程”。

功能

将线程标记为分离态(detached),线程终止时自动释放资源,无需pthread_join

函数原型

int pthread_detach(pthread_t thread);

两种分离方式

  1. 其他线程分离目标线程
    pthread_detach(tid); // tid为目标线程ID
    
  2. 线程自身分离
    pthread_detach(pthread_self());
    

示例

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void *thread_func(void *arg) {pthread_detach(pthread_self()); // 自身分离printf("Thread is running...\n");sleep(1);return NULL; // 终止后自动释放资源
}int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);sleep(2); // 等待线程执行// 无需pthread_join,线程已自动释放资源printf("Main thread exit\n");return 0;
}

总结

  • 创建pthread_create启动新线程,共享进程资源。
  • 终止returnpthread_exitpthread_cancel三种方式,灵活控制线程生命周期。
  • 等待pthread_join回收线程资源,获取退出状态。
  • 分离pthread_detach让线程自动释放资源,避免僵尸线程。

这些操作是 Linux 多线程编程的基础,需根据场景选择合适的线程控制方式。

4. 线程 ID 及进程地址空间布局

线程 ID 存在两种不同的 “身份标识”:一种是内核调度层面的线程 ID(LWP),另一种是线程库(NPTL)维护的用户态线程 ID。两者作用域和含义不同,需结合进程地址空间布局理解其本质。

4.1 两种线程 ID 的区分

1. 内核线程 ID(LWP)

  • 本质:内核级别的线程标识,是操作系统调度器识别线程的唯一标识(类似进程 ID,但针对线程)。
  • 特性
    • 系统全局唯一(整个系统中不会有两个线程拥有相同的 LWP)。
    • 属于进程调度范畴,内核通过 LWP 进行线程的调度和资源分配。
    • 可通过ps -aL命令查看(LWP列即为内核线程 ID)。
  • 示例:主线程的 LWP 与进程 ID(PID)相同,其他线程的 LWP 为独立数值。

2. 用户态线程 ID(pthread_t)

  • 本质:由 POSIX 线程库(NPTL,Native POSIX Thread Library)维护的线程标识,是用户态操作线程的 “句柄”。
  • 特性
    • 仅在进程内唯一(不同进程可存在相同的pthread_t值)。
    • 属于线程库范畴,线程库的所有操作(如pthread_joinpthread_cancel)均依赖此 ID。
    • 通过pthread_self()函数获取当前线程的pthread_t
  • 实现细节:在 Linux 的 NPTL 实现中,pthread_t本质是进程地址空间中的一个地址(指向线程控制块TCB的指针),线程库通过该地址访问线程的私有数据(如栈、寄存器上下文等)。

4.2 线程 ID 与进程地址空间的关系

进程地址空间为线程提供了共享与隔离的基础,线程 ID 的存储和使用与地址空间布局紧密相关:

进程地址空间布局

线程私有数据在地址空间中的位置

  • 主线程:栈位于进程地址空间的传统栈区(低地址附近),与进程的栈共享同一片区域。
  • 其他线程:栈由 pthread 库在共享库区域与堆之间动态分配(独立于主线程栈),保证线程栈的私有性。
  • 线程控制块(TCB):存储线程 ID(pthread_t指向的地址)、寄存器上下文、信号屏蔽字等私有信息,通常位于线程栈的顶部或单独的内存区域,属于进程地址空间的共享区(线程库管理)。

4.3 为何pthread_t是地址?

Linux 的 NPTL 线程库通过 “地址即标识” 的设计简化线程管理:

  • pthread_t直接指向线程的 TCB(线程控制块),线程库可通过该地址快速访问线程的私有数据(如栈指针、状态标志)。
  • 无需额外的全局表维护 ID 与线程的映射关系,减少了操作开销(如pthread_join可直接通过pthread_t找到线程的退出状态)。
  • 进程地址空间的独立性保证了pthread_t在进程内的唯一性(不同进程的地址空间隔离,同一地址在不同进程中对应不同线程)。

总结

  • 内核线程 ID(LWP):系统级唯一标识,用于内核调度,全局可见。
  • 用户态线程 ID(pthread_t):进程内唯一标识,本质是地址(指向 TCB),用于线程库操作。
  • 地址空间角色:线程共享进程的代码段、数据段、堆等,但其栈和 TCB(含pthread_t)位于私有区域,保证了执行独立性。

理解两种线程 ID 的区别,是掌握多线程调试(如gdb查看线程)和线程库原理的关键。

5. 线程封装

Thread.hpp

#ifndef THREAD_HPP
#define THREAD_HPP#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>namespace ThreadModlue
{static uint32_t number = 1;class Thread{using func_t = std::function<void()>;private:void EnableDetach(){std::cout << "线程被分离了" << std::endl;_isdetach = true;}void EnableRunning(){_isrunning = true;}static void *Routine(void *args){Thread *self = static_cast<Thread *>(args);self->EnableDetach();if (self->_isdetach)self->Detach();pthread_setname_np(self->_tid, self->_name.c_str());self->_func();return nullptr;}public:Thread(func_t func): _tid(0),_isdetach(false),_isrunning(false),res(nullptr),_func(func){_name = "Thread-" + std::to_string(number++);}void Detach(){if (_isdetach)return;if (_isrunning)pthread_detach(_tid);EnableDetach();}bool Start(){if (_isrunning)return false;int n = pthread_create(&_tid, nullptr, Routine, this);if (n != 0){std::cerr << "Failed to create thread: " << _name << std::endl;return false;}else{std::cout << _name << " started" << std::endl;}return true;}bool Stop(){if (_isrunning){int n = pthread_cancel(_tid);if (n != 0){std::cerr << "Failed to cancel thread: " << _name << std::endl;return false;}else{_isrunning = false;std::cout << _name << " stopped" << std::endl;}}return true;}void Join(){if (_isdetach)return;int n = pthread_join(_tid, &res);if (n != 0){std::cerr << "Failed to join thread: " << _name << std::endl;}else{std::cout << "Join successful" << std::endl;}}~Thread(){}private:pthread_t _tid;std::string _name;bool _isdetach;bool _isrunning;void *res;func_t _func;};
}#endif

Main.cc

#include "Thread.hpp"
#include <unistd.h>
#include <vector>using namespace ThreadModlue;int main()
{std::vector<Thread> threads;for (int i = 0; i < 10; i++){threads.emplace_back([](){while(true){char name[128];pthread_getname_np(pthread_self(), name, sizeof(name));std::cout << "我是一个新线程: " << name << std::endl; sleep(1);} });}for (auto &thread : threads){thread.Start();}for (auto &thread : threads){thread.Join();}return 0;
}

http://www.dtcms.com/a/462255.html

相关文章:

  • vue项目发布后图标乱码解决方案
  • 成都手机网站重庆本地建站
  • UI设计(二)赛博科技修仙通讯录——东方仙盟筑基期
  • 实时数仓历史数据优化
  • 网站建设在哪能看企业网站建立流程的第一步是什么
  • 告别手动配置:用 Terraform 定义你的 RustFS 存储帝国
  • 36.Linux Shell编程
  • AI智能体赋能社会科学研究领域之仿真:心智疆域的重塑与伦理韧性机制的建立
  • daily notes[81]
  • 常用命令和tricks
  • 【AI编程前沿】人类编写代码 vs AI生成代码:质量、漏洞与复杂度的大规模比较研究
  • 黑龙江建设人力资源网站网站建设及安全制度
  • 广州市增城建设局网站怎样开发一个app软件
  • 机器视觉Halcon3D中add_deformable_surface_model_reference_point的作用
  • 设计一个简单的旅游网站全网拓客app
  • 从零到一构建高可用微服务架构的核心实践与挑战
  • 【深入浅出PyTorch】--4.PyTorch基础实战
  • 项目源码安全审查怎么写
  • 陕西网站建设哪家专业培训班有哪些
  • 朋友给我做网站微网站开发第三方平台
  • 基于区块链的电子投票系统的设计与实现(源码+文档+部署讲解)
  • 百度对网站的收录课程网站建设内容
  • Linux修改MAC地址
  • GESP等级认证C++三级10-操作字符数组2-1
  • 上海网站推广wordpress 修改目录
  • [算法练习]Day 5:定长滑动窗口+变长滑动窗口
  • 华为OD机试C卷 - 完美走位 - 滑动窗口 - (Java C++ JavaScript Python)
  • 【JEECG】 子改主#表单值改变事件
  • wordpress密码hellogoogle搜索优化方法
  • 网站建设分析报告网站如何快速备案