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

《Linux线程——从概念到实践》

1. 线程的概念

在Linux中,线程 (Thread) 程序执行过程中最小的调度单位。线程是操作系统能够进行运算调度的基本单位,而进程是资源分配的基本单位。线程在同一个进程内共享资源(如内存、文件描述符等),但它们各自拥有独立的执行路径和程序计数器(PC)。线程的概念使得程序能够并发执行,提高效率。

1.2 为什么要使用线程?

在计算机世界里,程序的运行效率始终是开发者追求的核心目标之一。传统的单进程模型如同 “单线程工人”,一次只能处理一项任务,在面对复杂业务或高并发场景时显得力不从心。而 线程(Thread) 的出现,如同为程序配备了 “多线程工人团队”—— 它们共享资源却又能独立执行,让程序在效率与灵活性上实现了质的飞跃。

1.3 线程 VS 进程

维度进程线程
资源分配单位是(拥有独立内存、文件句柄等)否(共享进程资源)
调度单位是(但开销大,现代系统更倾向调度线程)是(操作系统直接调度的最小单位)
创建开销高(需分配内存、初始化资源)低(仅需创建线程栈和上下文)
通信复杂度高(需通过 IPC 机制,如管道、消息队列)低(可直接共享内存,无需额外机制)
影响范围进程崩溃不影响其他进程线程崩溃可能导致整个进程崩溃
应用服务器守护进程、需要强隔离的场景浏览器多标签页、即时通信软件、后端服务的线程池

1.4 Linux 如何实现线程:轻量级进程(LWP)的奥秘

LWP(LightweightProcess,轻量级进程)是一种在操作系统中实现线程的方式。它是介于传统进程(Process)和线程(Thread)之间的一种抽象概念, 用于表示操作系统中的一个调度单位。

LWP的特点:

  1. 共享资源:多个LWP通常属于同一个进程,它们共享进程的资源,如内存空间、文件描述符等,但每个LWP拥有独立的程序计数器、堆栈和局部变量。这意味着,LWP之间共享大部分资源,但仍可以独立执行
  2. 调度单位:LWP是操作系统调度的基本单位,虽然LWP之间共享进程的资源,但操作系统会根据各个LWP的状态进行独立调度。LWP能够在多核系统上并行执行,因此也称为轻量级线程。
  3. 对比线程和进程:进程是一个更重的实体,它有独立的内存空间和资源,且进程切换开销较大。线程(或LWP)是轻量级的调度单位,它们共享进程的资源,但每个线程都有自己的堆栈和执行上下文。

2.创建一个线程的完整流程

POSIX:pthread线程

创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void*(*start_routine)(void *), void *arg);
返回值:成功时返回 0,失败时返回错误码(如 EAGAIN 表示资源不足,EINVAL 表示参数无效)。
参数作用
pthread_t *thread输出参数,用于存储新创建线程的 ID(类型为 pthread_t)。
const pthread_attr_t *attr设置线程的属性(如栈大小、调度策略、分离状态等)。若为 NULL,则使用默认属性。
void* (start_routine)(void)线程的入口函数,新线程启动后将执行该函数。
void *arg传递给线程入口函数的参数(类型为 void*),支持任意类型的指针。

其他函数:

函数作用参数
void pthread_exit(void* retval);主动退出当前线程(线程自己调用,结束执行)retval : 线程的 “返回值”,类型是 void*,可传递任意数据(如字符串、结构体指针等)。若不需要返回值,填 NULL 即可。
int pthread_join(pthread_t thread, void **retval);阻塞等待指定线程结束(类似 “等人完成工作”)thread:要等待的线程的 ID(由 pthread_create 输出)。retval: 传入一个 void* 指针的地址(即 void** 类型),用于存储线程的返回值。若不需要返回值,填 NULL 即可。
int pthread_detach(pthread_t thread);分离线程。pthread_tthread:要分离的线程ID,使其资源在终止时自动回收。
pthread_t pthread_self(void);获取当前线程的 ID无参数,返回调用该函数的线程的 ID(pthread_t 类型),用于标识当前线程。
#include <pthread.h>
#include <stdio.h>void* thread_func(void* arg) {printf("Hello from thread! Argument: %s\n", (char*)arg);return NULL;
}int main() {pthread_t tid;char* msg = "World";// 创建线程,入口函数为thread_func,参数为msgint ret = pthread_create(&tid, NULL, thread_func, msg);if (ret != 0) {perror("pthread_create failed");return 1;}// 等待线程结束pthread_join(tid, NULL);return 0;
}

C++11 thread线程

使用方式很简单,这里不过多介绍

#include <iostream>
#include <thread>// 普通函数作为线程执行体
void threadFunction() {std::cout << "线程ID: " << std::this_thread::get_id() << std::endl;
}int main() {std::thread t(threadFunction);  // 创建并启动线程t.join();                       // 等待线程结束return 0;
}

参照:cplusplus网站

组件作用
std::thread线程对象,管理线程创建与生命周期
std::mutex互斥锁,保护共享资源
std::lock_guard自动管理互斥锁(构造加锁,析构解锁)
std::unique_lock灵活的锁管理(支持手动解锁、延迟加锁)
std::condition_variable线程间 “等待 - 通知” 通信
std::atomic原子类型,提供无锁的原子操作
join() / detach()线程同步与分离

两者区别

维度Linux 线程(pthread)C++ 线程(std::thread)
本质操作系统提供的底层线程接口(C 语言风格)C++ 标准库的跨平台线程抽象(面向对象风格)
跨平台性仅支持 POSIX 兼容系统(如 Linux、macOS)支持所有 C++11 及以上标准的平台(Linux、Windows、macOS 等)
使用方式基于函数和结构体(如 pthread_t、pthread_mutex_t),需手动管理资源(如检查返回值、释放锁)基于类和对象(如 std::thread、std::mutex),RAII 机制自动管理资源(如析构函数释放锁)
错误处理通过函数返回值(如 0 表示成功,非 0 表示错误码)通常通过抛出异常(如 std::system_error)
语言依赖依赖 C 语言接口,可在 C/C++ 中使用仅在 C++ 中可用,依赖 C++ 特性(如 lambda、模板)

3 多线程编程 同步与并发

如果两个或多个线程并发进行,同时访问和修改共享资源时,由于执行顺序的不确定性导致结果不可预测。

两个线程同时对全局变量 counter 执行 ++ 操作:
int count = 0;void* increment(void* arg) {for (int i = 0; i < 10000; i++) {count++; // 非原子操作(读取→修改→写入),可能导致结果错误}return NULL;
}

CPU 对count++的执行分为 “读 - 改 - 写” 三步,若两个线程的这三步交叉执行,会导致结果小于预期(如两个线程各执行 1 万次,最终counter可能小于 2 万)。

3.1 互斥锁

Mutex(互锁,MutualExclusion)是一种关键的同步机制,用于阻止许多个线程相同访问共享资源时,而避免了状态条(Race Condition)和数据不一致的问题。

函数作用
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutex_attr);初始化互斥锁
pthread_mutex_destroy(pthread_mutex_t *mutex)销毁互斥锁
pthread_mutex _lock(pthread_mutex_t *mutex)加锁(如果已被锁,则阻塞)
pthread_mutex_trylock(pthread_mutex_t *mutex)尝试加锁(不会阻塞)
pthread_mutex_unlock(pthread_mutex_t *mutex)解锁

其中,在加锁过程中,pthread_mutex_lock()函数和 pthread_mutex_trylock()函数的过程略有不同:
1.当使用pthread_mutex_lock()函数进行加锁时,若此时已经被锁,则尝试加锁的线程会被阻塞,直到互斥锁被其他线程释放,当pthread_mutex_lock0函数有返回值时,说明加锁成功;
2. 而使用pthread_mutex_trylock()函数进行加锁时,若此时已经被锁,则会返回EBUSY的错误码。

1. 创建锁
pthread_mutex_t mutex;  // 定义互斥锁// 方法一:静态初始化(使用默认属性)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 方法二:动态初始化(可设置属性)
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);  // NULL 表示使用默认属性2. 上锁
// 阻塞式加锁:若锁被占用,则线程阻塞等待
pthread_mutex_lock(&mutex);// 非阻塞式加锁:立即返回,成功返回0,失败返回EBUSY
int ret = pthread_mutex_trylock(&mutex);
if (ret == 0) {// 加锁成功
} else if (ret == EBUSY) {// 锁被占用,处理冲突
}3. 解锁
pthread_mutex_unlock(&mutex);  // 释放锁,唤醒等待线程4. 销毁锁
pthread_mutex_destroy(&mutex);  // 释放互斥锁资源
给刚才的代码加入互斥锁
pthread_mutex_t mutex;
int counter = 0;void* increment(void* arg) {for (int i = 0; i < 10000; i++) {pthread_mutex_lock(&mutex);   // 加锁counter++;                    // 临界区pthread_mutex_unlock(&mutex); // 解锁}return NULL;
}

在c++11中引入了更方便的mutex,想要更多了解可看:
《C++11 多线程必学:std::mutex 详解与实战案例》

3.2 条件变量

在多线程场景中,线程常需等待特定条件(如数据就绪、资源释放)才能继续执行。若用轮询检查实现,会导致 CPU 空转,浪费系统资源。条件变量则提供了更高效的方案
条件变量需搭配互斥锁来使用:
条件变量用于等待 / 通知 “某个条件成立”,而条件的判断依赖共享变量(如生产者 - 消费者模型里的 “队列是否非空”)。这些共享变量属于临界资源,若不通过互斥锁保护,可能出现:

  1. 线程 A 判断 “条件不满足” 准备等待,此时线程 B 修改条件并通知,但线程 A 已错过通知,导致永久阻塞。
  2. 多个线程同时修改条件,引发数据竞争(如队列长度被并发修改)。
函数作用
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);初始化条件变量,为后续线程同步操作做准备。
int pthread_cond_destroy(pthread_cond_t *cond);销毁条件变量,释放相关资源,通常在不再需要条件变量时调用。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);让线程阻塞等待条件满足,需配合互斥锁(pthread_mutex_t )使用,会原子性解锁互斥锁并等待,条件满足时重新加锁并返回。
int pthread_cond_signal(pthread_cond_t *cond);唤醒一个因该条件变量等待的线程,若有多个等待线程,按调度策略选一个唤醒。
pthread_cond_broadcast(pthread_cond_t *cond)唤醒所有因该条件变量等待的线程,常用于需所有等待线程响应的场景 。

示例代码(封装cond类)

#pragma once#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"
//通过 C++ 类封装 POSIX 条件变量 API,简化条件变量的使用,实现线程间的等待 / 通知机制
//条件变量在构造函数中初始化(pthread_cond_init),在析构函数中销毁(pthread_cond_destroy),符合 RAII 原则,避免资源泄漏。
namespace CondModule
{using namespace LockModule;class Cond{public:Cond()//初始化条件变量_cond。{int n = ::pthread_cond_init(&_cond, nullptr);(void)n;}//Wait() 函数接收 Mutex 对象引用,通过 mutex.LockPtr() 获取底层 pthread_mutex_t*,确保条件变量与互斥锁正确配合使用。void Wait(Mutex &mutex){int n = ::pthread_cond_wait(&_cond, mutex.LockPtr());}void Notify()//唤醒一个等待该条件变量的线程。{int n = ::pthread_cond_signal(&_cond);(void)n;}void NotifyAll()//唤醒所有等待该条件变量的线程。{int n = ::pthread_cond_broadcast(&_cond);(void)n;}~Cond(){int n = ::pthread_cond_destroy(&_cond);}private:pthread_cond_t _cond;};
}

在c++11中引入了更方便的条件变量condition_variable,想要更多了解可看:
《Linux 环境下 C++ 条件变量与 POSIX 条件变量详解》

3.3 读写锁

读写锁(Read - Write Lock)是一种用于解决并发访问共享资源时同步问题的同步机制,尤其适用于读操作远多于写操作的场景,能有效提升并发性能。

3.3.1 核心概念与状态

读写锁把对共享资源的访问者分为读者(Reader)和写者(Writer):

  1. 读锁(共享锁):允许多个线程同时持有,用于纯读操作(不修改资源),因为读操作本身不影响数据一致性,并发读可提升效率。
  2. 写锁(排他锁 / 独占锁):同一时刻仅允许一个线程持有,用于写操作(修改资源),保证写操作的原子性,避免多线程写导致数据混乱。
  3. 读写锁有三种状态:读模式加锁、写模式加锁、不加锁 。

3.3.2 加锁规则(核心逻辑)

写锁优先时(常见默认行为,也可配置公平策略):

  1. 若读写锁处于写加锁状态:所有后续尝试加读锁或写锁的线程都会被阻塞,直到写锁释放。保证写操作的原子性,避免写过程中被其他线程干扰。
  2. 若读写锁处于读加锁状态:
    新的读锁请求可直接成功(多线程共享读锁);
    新的写锁请求会被阻塞,直到所有读锁都释放(避免读 - 写冲突,保证写操作能修改 “干净” 的数据 )。
  3. 特殊情况:若读锁已持有,新写锁请求到来后,后续读锁请求可能被 “限流”(部分实现会让新读锁等待,防止写请求长期阻塞,即 “写优先” 或 “读写公平” 策略 )。

公平策略下:严格按照线程请求顺序分配锁,写锁请求和读锁请求排队,避免某类操作(如读)长期占优导致另一类操作(如写)饥饿。

3.3.3 实例

在这里插入图片描述

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>// 读写锁、互斥锁、共享数据
pthread_rwlock_t rwlock;
int shared_data = 0;// 读操作线程函数
void* read_task(void* arg) {while (1) {// 获取读锁pthread_rwlock_rdlock(&rwlock);printf("Read: shared_data = %d\n", shared_data);// 释放读锁pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}// 写操作线程函数
void* write_task(void* arg) {int count = 0;while (1) {// 获取写锁pthread_rwlock_wrlock(&rwlock);shared_data = ++count;printf("Write: shared_data updated to %d\n", shared_data);// 释放写锁pthread_rwlock_unlock(&rwlock);sleep(3);}return NULL;
}int main() {// 初始化读写锁pthread_rwlock_init(&rwlock, NULL);pthread_t read_thread1, read_thread2, write_thread;// 创建读线程和写线程pthread_create(&read_thread1, NULL, read_task, NULL);pthread_create(&read_thread2, NULL, read_task, NULL);pthread_create(&write_thread, NULL, write_task, NULL);// 等待线程结束(实际需更优雅退出)pthread_join(read_thread1, NULL);pthread_join(read_thread2, NULL);pthread_join(write_thread, NULL);// 销毁读写锁pthread_rwlock_destroy(&rwlock);return 0;
}

4. 实战

4.1 封装原Thread接口(区别于C++ 标准库的线程)

简化原生pthread接口的使用,提供更面向对象、更安全的线程操作方式。

#ifndef _THREAD_HPP__
#define _THREAD_HPP__#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>// 简化原生pthread接口的使用,提供更面向对象、更安全的线程操作方式。
// 通过std::function支持任意可调用对象作为线程函数,比原生pthread只能使用静态函数更灵活。// v1
namespace ThreadModule
{// 标准化线程执行的函数签名(接受字符串参数,无返回值)using func_t = std::function<void(std::string name)>;// 为每个线程生成唯一ID的计数器static int number = 1;// 定义线程生命周期状态enum class TSTATUS{NEW,     // 线程已创建但未运行RUNNING, // 线程正在运行STOP     // 线程已停止};class Thread{private:// 成员方法!// 线程执行的静态函数,作为pthread_create的入口static void *Routine(void *args){// 将void*参数转换回Thread对象Thread *t = static_cast<Thread *>(args);// 设置线程状态为运行中t->_status = TSTATUS::RUNNING;// 执行用户传入的函数对象,将线程名称作为参数传入t->_func(t->Name());return nullptr;}// 启用线程分离,将_joinable标记为falsevoid EnableDetach() { _joinable = false; }public:// 构造函数,接受一个可调用对象作为线程执行的任务Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true){// 为线程生成唯一的名称_name = "Thread-" + std::to_string(number++);// 获取当前进程的ID_pid = getpid();}// 启动线程bool Start(){// 只有当线程状态不是RUNNING时才能启动if (_status != TSTATUS::RUNNING){// 创建线程,将当前Thread对象作为参数传递给Routine函数int n = ::pthread_create(&_tid, nullptr, Routine, this);// 如果创建失败,返回falseif (n != 0)return false;return true;}return false;}// 停止线程bool Stop(){// 只有当线程状态为RUNNING时才能停止if (_status == TSTATUS::RUNNING){// 取消线程int n = ::pthread_cancel(_tid);// 如果取消失败,返回falseif (n != 0)return false;// 设置线程状态为停止_status = TSTATUS::STOP;return true;}return false;}// 等待线程结束bool Join(){// 只有当线程是可连接状态时才能调用joinif (_joinable){// 等待线程结束int n = ::pthread_join(_tid, nullptr);// 如果等待失败,返回falseif (n != 0)return false;// 设置线程状态为停止_status = TSTATUS::STOP;return true;}return false;}// 分离线程void Detach(){// 启用线程分离EnableDetach();// 分离线程pthread_detach(_tid);}// 判断线程是否是可连接状态bool IsJoinable() { return _joinable; }// 获取线程的名称std::string Name() { return _name; }// 析构函数~Thread(){}private:std::string _name; // 线程的名称pthread_t _tid;    // 线程的IDpid_t _pid;        // 进程的IDbool _joinable;    // 是否是分离的,默认不是,标记线程是否为“可连接”(joinable)状态。func_t _func;      // 存储线程实际执行的函数(任务)。TSTATUS _status;   // 记录线程的当前状态。};
}#endif

4.2 线程池

《深入剖析 C++ 线程池实现》

注意:使用的都是C++ 标准库的线程,互斥锁,条件变量,建议先了解!

4.3 力扣题目

测试题目 按序打印, H2O 生成

如有错误,欢迎指正!


文章转载自:

http://jy8Olbgx.zpkfb.cn
http://JBEWMHDT.zpkfb.cn
http://TzxTxdVb.zpkfb.cn
http://jNOnfKXb.zpkfb.cn
http://NpAkA1dp.zpkfb.cn
http://4nTU24QI.zpkfb.cn
http://TMNDd2hs.zpkfb.cn
http://EqUnT5fT.zpkfb.cn
http://bATUYgDB.zpkfb.cn
http://aaEiviDY.zpkfb.cn
http://HVaxajui.zpkfb.cn
http://gZO2P1y3.zpkfb.cn
http://LLMm6hR0.zpkfb.cn
http://jnEVohJQ.zpkfb.cn
http://CTfJ9rE8.zpkfb.cn
http://0G3OX5io.zpkfb.cn
http://zvar04h2.zpkfb.cn
http://oOpzXcwG.zpkfb.cn
http://cy4K4Lv5.zpkfb.cn
http://l4vdGEAX.zpkfb.cn
http://y3ygfKQd.zpkfb.cn
http://hgcr2xOY.zpkfb.cn
http://uvdxcwGa.zpkfb.cn
http://3cbRBjBA.zpkfb.cn
http://kKlBAmBu.zpkfb.cn
http://7qqwU1DL.zpkfb.cn
http://ETwnCjeQ.zpkfb.cn
http://0jAT9bWs.zpkfb.cn
http://kFgeE6HY.zpkfb.cn
http://PVLeHI8n.zpkfb.cn
http://www.dtcms.com/a/381377.html

相关文章:

  • Android相机API2,基于GLSurfaceView+SurfaceTexture实现相机预览,集成的相机算法采用GPU方案,简要说明
  • 美团核销接口,第三方服务商零侵入对接的核心步骤与技巧美团核销接口
  • Java导出复杂excel,自定义excel导出
  • 【SLT库】红黑树的原理学习 | 模拟实现
  • 【轨物方案】赋能绿色能源新纪元:轨物科技发布光伏清洁机器人智能控制与运维解决方案
  • React Hooks原理深度解析与高级应用模式
  • React 原理篇 - 深入理解虚拟 DOM
  • [能源化工] 面向锂电池RUL预测的开源项目全景速览
  • 分布式专题——10.5 ShardingSphere的CosID主键生成框架
  • 【Redis#9】其他数据结构
  • C++使用拉玛努金公式计算π的值
  • 上海市2025CSP-J十连测Round 5卷后感
  • RDB/AOF------Redis两大持久化方法
  • 【图解】idea中快速查找maven冲突
  • Dubbo SPI机制
  • 《Linux 基础指令实战:新手入门的命令行操作核心教程(第一篇)》
  • 【开题答辩全过程】以 “饭否”食材搭配指南小程序的设计与实现为例,包含答辩的问题和答案
  • RabbitMQ 在实际开发中的应用场景与实现方案
  • 有没有什么办法能批量去除很多个PDF文件的水印
  • JavaScript 内存管理与常见泄漏排查(闭包、DOM 引用、定时器、全局变量)
  • ArkAnalyzer源码初步分析I——分析ts项目流程
  • Linux_基础指令(二)
  • 什么是子网?
  • 【前端】【utils】高效文件下载技术解析
  • FastAPI 中内省函数 inspect.signature() 作用
  • 【Linux】Linux进程概念(上)
  • 前端vue使用canvas封装图片标注功能,鼠标画矩形框,标注文字 包含下载标注之后的图片
  • 水库运行综合管理平台
  • langgraph astream使用详解
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(31):文法運用第9回3+(考え方11)