Linux探秘坊-------15.线程

1.线程概念

1.什么是线程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.线程 vs 进程

在这里插入图片描述
不同的操作系统有不同的实现方式:

  • linux :直接使用pcb的功能来模拟线程,不创建新的数据结构
  • windows: 使用新的数据结构TCB,来进行实现,一个PCB里有很多个TCB

3.资源划分

详情可见操作系统书籍中的存储器管理虚拟存储器管理章节!!!!

4.线程理解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.线程和进程的区别

在这里插入图片描述

  • 黄字代表线程特有的私有数据
  • 一组寄存器和上下文数据---------------证明线程是可以被独立调用
  • 栈--------------证明线程是动态的

1.进程的线程共享!!!!!!!!!!

同⼀地址空间,因此 Text Segment 、 Data Segment 都是共享的,如果定义⼀个函数,在各线程中都可以调⽤ ,如果定义⼀个全局变量,在各线程中都可以访问到, 除此之外,各线程还共享以下进程资源和环境:

  • ⽂件描述符表 (fd)
  • 每种信号的处理⽅式(SIG_IGN、SIG_DFL或者⾃定义的信号处理函数)
  • 当前⼯作⽬录
  • ⽤⼾id和组id

1.父子进程只有代码段是共享的,但主线程和子线程连地址空间都是共享的,所以他们可以使用共享的函数和全局变量 意思就是如果子线程的全局变量被修改了,主线程看到的是同一个全局变量,也会变化 轻松实现类似进程间通信!!!!!!!
2.而全局变量在父子进程中是写实拷贝,子变父不变 !!!!!!!!!

在这里插入图片描述

3.linux的线程控制

1.线程创建

创建函数:
在这里插入图片描述
运行后使用 ps - aL指令查看线程
在这里插入图片描述

2.pthread库的引入----为什么需要有线程库?

在这里插入图片描述

4.pthread库的使用

  • 与线程有关的函数构成了⼀个完整的系列,绝⼤多数函数的名字都是“pthread_”打头的
    • 要使⽤这些函数库,要通过引⼊头⽂件 <pthread.h>
    链接这些线程函数库时要使⽤编译器命令的“-lpthread”选项

1.线程创建—pthread_create()

在这里插入图片描述

  • thread是新线程的标识符,是输出型参数(让主线程获取,便于调用其他函数)

2.线程等待—pthread_join()

在这里插入图片描述

  • 这里retval拿到的是子线程的退出码,即子线程函数的返回值,但返回值是void *
  • 所以retval的类型应当是void* 的地址类型即void**

在这里插入图片描述

  • 其中,routine是子线程的入口函数,routine函数结束的话子线程也就结束了

3.线程取消或终止—pthread_exit()/pthread_cancel()

1-----------------pthread_exit()
在这里插入图片描述
2-----------------pthread_cancel()

在这里插入图片描述

  • 如果线程被主线程或其他线程取消,那么主线程join函数得到的返回值固定为-1

4.线程分离—int pthread_detach(pthread_t thread);

在这里插入图片描述

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

1.--------------------------pthread_self函数获取id
在这里插入图片描述
2.--------------------------pthread库的动态链接
在这里插入图片描述

  • 所有的线程都是在thread库中建立的,线程的管理块都存储在库中具体的pcb由用户使用系统调用在内核中建立

在这里插入图片描述

  • 创建线程的具体图例

在这里插入图片描述

6.线程互斥

来看一个买票的例子:

/ 操作共享变量会有问题的售票系统代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int ticket = 100;
void* route(void* arg) {
    char* id = (char*)arg;
    while (1) {
        if (ticket > 0) {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
        } else {
            break;
        }
    }
}
int main(void) {
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, NULL, route, (void*)"thread 1");
    pthread_create(&t2, NULL, route, (void*)"thread 2");
    pthread_create(&t3, NULL, route, (void*)"thread 3");
    pthread_create(&t4, NULL, route, (void*)"thread 4");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
}

在这里插入图片描述

  • 为了避免买票变成负数,所以要使用 来保证线程间的互斥

为什么会变成负数??????

  • 因为ticket–操作并不是原子的----------即无法一步完成------要先把ticket大小传入cpu,再在cpu中进行运算,最后再写回ticket全局变量中--------一共有三步
  • 可能某一个线程计算完后ticket为0,但还没写回ticket,另一个线程就又开始运行,这个时候ticket是1,还能通过if条件语句,最后两个线程都执行ticket–,全部写回后,ticket变成了-1!!!!

在这里插入图片描述

1.锁的使用

#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int ticket = 100;
pthread_mutex_t mutex;//设置锁----这里是全局锁,用完后会自动销毁
void* route(void* arg) 
{
    char* id = (char*)arg;
    while (1) 
    {
        pthread_mutex_lock(&mutex);//pthread_mutex_lock--------上锁
        if (ticket > 0) {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
            pthread_mutex_unlock(&mutex);// pthread_mutex_unlock--------解锁
        } else {
            pthread_mutex_unlock(&mutex);// pthread_mutex_unlock--------解锁
            break;
        }
    }
    return nullptr;
}
int main(void) {
    pthread_t t1, t2, t3, t4;
    pthread_mutex_init(&mutex, NULL);// pthread_mutex_init------初始化锁
    pthread_create(&t1, NULL, route, (void*)"thread 1");
    pthread_create(&t2, NULL, route, (void*)"thread 2");
    pthread_create(&t3, NULL, route, (void*)"thread 3");
    pthread_create(&t4, NULL, route, (void*)"thread 4");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    pthread_mutex_destroy(&mutex);//pthread_mutex_destroy-------删除锁
}

pthread 库是 POSIX 线程库,提供了多线程编程的 API,其中包括用于线程同步的锁机制。以下是 pthread 中常见的锁函数及其解释:


1. 互斥锁(Mutex)

互斥锁用于保护临界区,确保同一时间只有一个线程可以访问共享资源。

相关函数:
  • pthread_mutex_init
    初始化互斥锁。

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
    
    • mutex:指向互斥锁对象的指针。
    • attr:属性对象(通常设为 NULL 使用默认属性)。
    • 成功返回 0,失败返回错误码。
  • pthread_mutex_lock
    加锁。如果锁已被其他线程持有,则调用线程阻塞直到锁被释放。

    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
  • pthread_mutex_trylock
    尝试加锁,如果锁已被持有则立即返回错误(非阻塞)。

    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    
    • 成功返回 0,锁被持有时返回 EBUSY
  • pthread_mutex_unlock
    解锁。

    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
  • pthread_mutex_destroy
    销毁互斥锁,释放资源。

    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

7.线程同步

在这里插入图片描述

1.条件变量函数介绍

-------------------为了避免加锁后导致线程饥饿而设置的变量

条件变量用于线程间通信,通常与互斥锁配合使用,实现线程的等待和唤醒机制。

相关函数:
  • pthread_cond_init //第二个变量基本是设置为nullptr
    初始化条件变量。

    int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
    
  • pthread_cond_wait // 第二个参数是上文的互斥锁
    等待条件变量,并释放关联的互斥锁(原子操作)。

    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    
  • pthread_cond_signal
    唤醒一个等待该条件变量的线程。

    int pthread_cond_signal(pthread_cond_t *cond);
    
  • pthread_cond_broadcast
    唤醒所有等待该条件变量的线程。

    int pthread_cond_broadcast(pthread_cond_t *cond);
    
  • pthread_cond_destroy
    销毁条件变量。

    int pthread_cond_destroy(pthread_cond_t *cond);
    
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <pthread.h>

#define NUM 5
int cnt = 1000;

pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 定义锁
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;   // 定义条件变量

// 等待是需要等,什么条件才会等呢?票数为0,等待之前,就要对资源的数量进行判定。
// 判定本身就是访问临界资源!,判断一定是在临界区内部的.
// 判定结果,也一定在临界资源内部。所以,条件不满足要休眠,一定是在临界区内休眠的!
// 证明一件事情:条件变量,可以允许线程等待
// 可以允许一个线程唤醒在cond等待的其他线程, 实现同步过程
void *threadrun(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        pthread_mutex_lock(&glock);
        // 直接让对用的线程进行等待?? 临界资源不满足导致我们等待的!
        pthread_cond_wait(&gcond, &glock); // glock在pthread_cond_wait之前,会被自动释放掉
        std::cout << name << " 计算: " << cnt << std::endl;
        cnt++;
        pthread_mutex_unlock(&glock);
    }
}

int main()
{
    std::vector<pthread_t> threads;
    for (int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        char *name = new char[64];
        snprintf(name, 64, "thread-%d", i);//snprintf函数往name中打印字符串
        int n = pthread_create(&tid, nullptr, threadrun, name);
        if (n != 0)
            continue;
        threads.push_back(tid);
        sleep(1);
    }

    sleep(3);

    // 每隔1s唤醒一个线程
    while(true)
    {
        std::cout << "唤醒所有线程... " << std::endl;
        pthread_cond_broadcast(&gcond);
        
        // std::cout << "唤醒一个线程... " << std::endl;
        // pthread_cond_signal(&gcond);
        sleep(1);
    }

    for (auto &id : threads)
    {
        int m = pthread_join(id, nullptr);
        (void)m;
    }

    return 0;
}

在这里插入图片描述
在这里插入图片描述

8.生产者消费者模型

在这里插入图片描述
在这里插入图片描述

  • 生产者和消费者之间要有顺序地进行工作,所以是同步的
1.基于阻塞队列实现生产者消费者模型:

----------------什么是阻塞队列?

在这里插入图片描述

blockqueue.hpp:

// 阻塞队列的实现
#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>

const int defaultcap = 5; // for test 默认队列容量,用于测试

// 模板类定义,T为队列元素类型
template <typename T>
class BlockQueue
{
private:
    // 判断队列是否已满
    bool IsFull() { return _q.size() >= _cap; }
    
    // 判断队列是否为空
    bool IsEmpty() { return _q.empty(); }

public:
    // 构造函数,初始化队列容量和同步变量
    BlockQueue(int cap = defaultcap)
        : _cap(cap), _csleep_num(0), _psleep_num(0)
    {
        pthread_mutex_init(&_mutex, nullptr);       // 初始化互斥锁
        pthread_cond_init(&_full_cond, nullptr);    // 初始化"满"条件变量
        pthread_cond_init(&_empty_cond, nullptr);   // 初始化"空"条件变量
    }
    
    // 入队操作,生产者调用
    void Equeue(const T &in)
    {
        pthread_mutex_lock(&_mutex);  // 加锁保护临界区
        
        // 生产者调用
        while (IsFull())  // 循环检查队列是否已满(防止虚假唤醒),这里一定要使用while,不能使用if
        {
            // 队列已满,生产者需要等待
            // 应该让生产者线程进行等待
            // 重点1:pthread_cond_wait调用成功,挂起当前线程之前,要先自动释放锁!!
            // 重点2:当线程被唤醒的时候,默认就在临界区内唤醒!要从pthread_cond_wait
            // 成功返回,需要当前线程,重新申请_mutex锁!!!
            // 重点3:如果我被唤醒,但是申请锁失败了??我就会在锁上阻塞等待!!!
            _psleep_num++;  // 增加休眠生产者计数
            std::cout << "生产者,进入休眠了: _psleep_num" <<  _psleep_num << std::endl;
            
            // 等待队列不满的信号
            // 1. 调用时会自动释放锁
            // 2. 被唤醒时会重新获取锁
            // 3. 可能因为虚假唤醒或获取锁失败而阻塞
            pthread_cond_wait(&_full_cond, &_mutex);
            _psleep_num--;  // 生产者被唤醒,减少计数
        }
        
        // 队列有空间,执行入队操作
        _q.push(in);

        // v2优化: 如果有消费者在等待,则唤醒一个消费者
        if(_csleep_num>0)
        {
            pthread_cond_signal(&_empty_cond);
            std::cout << "唤醒消费者..." << std::endl;
        }

        pthread_mutex_unlock(&_mutex); // 释放锁
    }
    
    // 出队操作,消费者调用
    T Pop()
    {
        pthread_mutex_lock(&_mutex);  // 加锁保护临界区
        
        // 消费者调用
        while (IsEmpty())  // 循环检查队列是否为空(防止虚假唤醒)
        {
            _csleep_num++;  // 增加休眠消费者计数
            pthread_cond_wait(&_empty_cond, &_mutex);  // 等待队列不空的信号
            _csleep_num--;   // 消费者被唤醒,减少计数
        }
        
        // 队列不空,执行出队操作
        T data = _q.front();
        _q.pop();

        // 如果有生产者在等待,则唤醒一个生产者
        if(_psleep_num > 0)
        {
            pthread_cond_signal(&_full_cond);
            std::cout << "唤醒生产者" << std::endl;
        }

        pthread_mutex_unlock(&_mutex);  // 释放锁
        return data;  // 返回出队元素
    }
    
    // 析构函数,释放资源
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);     // 销毁互斥锁
        pthread_cond_destroy(&_full_cond);  // 销毁"满"条件变量
        pthread_cond_destroy(&_empty_cond); // 销毁"空"条件变量
    }

private:
    std::queue<T> _q; // 底层队列容器,存储实际数据(临界资源)
    int _cap;         // 队列容量大小

    pthread_mutex_t _mutex;     // 互斥锁,保护队列操作
    pthread_cond_t _full_cond;  // 条件变量,队列满时生产者等待
    pthread_cond_t _empty_cond; // 条件变量,队列空时消费者等待

    int _csleep_num; // 当前休眠的消费者线程数量
    int _psleep_num; // 当前休眠的生产者线程数量
};

Task.hpp:

// 防止头文件重复包含
#pragma once

// 包含标准输入输出库
#include <iostream>
// 包含Unix标准库,提供sleep等函数
#include <unistd.h>
// 包含函数对象库,用于std::function
#include <functional>

// 任务形式2
// 我们定义了一个任务类型,返回值void,参数为空
using task_t = std::function<void()>;

// 下载任务示例函数
void Download()
{
    // 打印任务开始信息
    std::cout << "我是一个下载任务..." << std::endl;
    // 模拟耗时操作,暂停3秒
    sleep(3); // 假设处理任务比较耗时
}

// 任务形式1
// 任务类定义
class Task
{
public:
    // 默认构造函数
    Task(){}
    
    // 带参数的构造函数,初始化x和y
    Task(int x, int y):_x(x), _y(y)
    {
    }
    
    // 执行任务的方法
    void Execute()
    {
        // 计算x和y的和
        _result = _x + _y;
    }
    
    // 获取x值的方法
    int X() { return _x; }
    
    // 获取y值的方法
    int Y() { return _y; }
    
    // 获取计算结果的方法
    int Result()
    {
        return _result;
    }
    
private:
    int _x;       // 第一个操作数
    int _y;       // 第二个操作数
    int _result;  // 计算结果
};

Main.cc:

// 包含阻塞队列头文件
#include "BlockQueue.hpp"
// 包含任务定义头文件
#include "Task.hpp"
// 标准输入输出库
#include <iostream>
// POSIX线程库
#include <pthread.h>
// Unix标准库,提供sleep等函数
#include <unistd.h>

// 消费者线程函数
void *consumer(void *args)
{
    // 将传入参数转换为阻塞队列指针
    BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);

    // 消费者持续运行
    while (true)
    {
        sleep(10); // 模拟消费者处理速度较慢
        
        // 1. 从阻塞队列中取出任务
        task_t t = bq->Pop();

        // 2. 执行任务
        // 注意:此时任务已经从队列中移除,在消费者线程上下文中执行
        t();
    }
    return nullptr; // 线程函数需要返回指针
}

// 生产者线程函数
void *productor(void *args)
{
    // 将传入参数转换为阻塞队列指针
    BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);
    
    // 生产者持续运行
    while (true)
    {
        // 1. 生产任务前的提示信息
        std::cout << "生产了一个任务: " << std::endl;

        // 2. 将任务放入阻塞队列
        // 这里使用Download函数作为示例任务
        bq->Equeue(Download);
    }
    return nullptr; // 线程函数需要返回指针
}

// 主函数
int main()
{
    // 创建阻塞队列实例
    // 队列中存储的是task_t类型的任务(std::function<void()>)
    BlockQueue<task_t> *bq = new BlockQueue<task_t>();

    // 定义线程ID数组
    pthread_t c[2]; // 2个消费者线程
    pthread_t p[3]; // 3个生产者线程

    // 创建消费者线程
    pthread_create(c, nullptr, consumer, bq);
    pthread_create(c+1, nullptr, consumer, bq);
    
    // 创建生产者线程
    pthread_create(p, nullptr, productor, bq);
    pthread_create(p+1, nullptr, productor, bq);
    pthread_create(p+2, nullptr, productor, bq);

    // 等待所有线程结束(实际上由于是无限循环,不会结束)
    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(p[2], nullptr);

    // 释放阻塞队列内存(实际上由于线程无限循环,不会执行到这里)
    delete bq;
    return 0;
}
2.基于环形队列实现生产者消费者模型:

-------POSIX信号量:

POSIX信号量和SystemV信号量作⽤相同,都是⽤于同步操作,达到⽆冲突的访问共享资源⽬的。但POSIX可以⽤于线程间同步

在这里插入图片描述

  • 上文写的基于阻塞队列的生产消费模型就是第一种,因为无论是消费者还是生产者,同一个时间下 只能有一个线程访问阻塞队列这个临界资源 !!!!!!-------------------所以说是整体使用的,所以要加mutex锁!!!!
  • 下面的可以对资源进行切片,实现多个线程共享资源(不同块),需要使用信号量------pv操作--------p--,v++

在这里插入图片描述
在这里插入图片描述

POSIX信号量的调用接口

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
 pshared:0表⽰线程间共享,⾮零表⽰进程间共享//线程间共享直接传nullptr
 value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量 :**p()------说白了就是-- **

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()----说白了就是--

发布信号量 v()------说白了就是++

功能:发布信号量,表⽰资源使⽤完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);//V()

Mutex.hpp------自己写的互斥锁:

// 防止头文件重复包含
#pragma once

// 标准输入输出库
#include <iostream>
// POSIX线程库
#include <pthread.h>

// 定义互斥锁模块命名空间
namespace MutexModule
{
    // 互斥锁类封装
    class Mutex
    {
    public:
        // 构造函数:初始化互斥锁
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr); // 使用默认属性初始化互斥锁
        }
        
        // 加锁方法
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex); // 阻塞直到获取锁
            (void)n; // 显式忽略返回值,避免编译器警告
        }
        
        // 解锁方法
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex); // 释放锁
            (void)n; // 显式忽略返回值,避免编译器警告
        }
        
        // 析构函数:销毁互斥锁
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex); // 释放互斥锁资源
        }
        
        // 获取底层pthread_mutex_t指针
        pthread_mutex_t *Get()
        {
            return &_mutex; // 暴露底层POSIX互斥锁,用于需要原生接口的场景
        }
        
    private:
        pthread_mutex_t _mutex; // POSIX原生互斥锁对象
    };

    // 锁守卫类(RAII封装)
    class LockGuard
    {
    public:
        // 构造函数:接收Mutex引用并立即加锁!!!!!!
        LockGuard(Mutex &mutex):_mutex(mutex)
        {
            _mutex.Lock(); // 构造时自动加锁
        }
        
        // 析构函数:自动解锁----------析构时自动解锁
        ~LockGuard()
        {
            _mutex.Unlock(); // 析构时自动解锁
        }
        
    private:
        Mutex &_mutex; // 引用管理的互斥锁对象
    };
}

Sem.hpp---------自己写的条件变量


// 包含标准输入输出库
#include <iostream>
// 包含POSIX信号量库
#include <semaphore.h>
// 包含POSIX线程库
#include <pthread.h>

// 定义信号量模块命名空间
namespace SemModule
{
    // 默认信号量初始值
    const int defaultvalue = 1;
    
    // 信号量类封装
    class Sem
    {
    public:
        // 构造函数:初始化信号量
        // 参数sem_value: 信号量初始值,默认为defaultvalue(1)
        Sem(unsigned int sem_value = defaultvalue)
        {
            // 初始化无名信号量
            // 参数1: 信号量对象
            // 参数2: pshared标志(0表示线程间共享,非0表示进程间共享)
            // 参数3: 信号量初始值
            sem_init(&_sem, 0, sem_value);
        }
        
        // P操作(等待/获取信号量)--
        void P()
        {
            // 原子操作,信号量值减1
            // 如果信号量值为0,则阻塞直到信号量值大于0
            int n = sem_wait(&_sem);
            (void)n; // 显式忽略返回值,避免编译器警告
        }
        
        // V操作(释放/增加信号量)++
        void V()
        {
            // 原子操作,信号量值加1
            // 如果有线程在等待信号量,则唤醒其中一个
            int n = sem_post(&_sem);
            (void)n; // 显式忽略返回值,避免编译器警告
        }
        
        // 析构函数:销毁信号量
        ~Sem()
        {
            // 销毁信号量,释放相关资源
            sem_destroy(&_sem);
        }

    private:
        sem_t _sem; // POSIX信号量对象
    };
}

RingQueue.hpp:循环队列

// 防止头文件重复包含
#pragma once

// 标准输入输出库
#include <iostream>
// 动态数组容器
#include <vector>
// 包含信号量实现
#include "Sem.hpp"
// 包含互斥锁实现
#include "Mutex.hpp"

// 调试用的默认队列容量
static const int gcap = 5; // for debug

// 使用信号量和互斥锁的命名空间
using namespace SemModule;
using namespace MutexModule;

// 环形队列模板类
template <typename T>
class RingQueue
{
public:
    // 构造函数,初始化队列容量和同步机制
    RingQueue(int cap = gcap)
        : _cap(cap),          // 设置队列容量
          _rq(cap),           // 初始化存储容器
          _blank_sem(cap),    // 初始化空位信号量(初始值为容量)----N
          _p_step(0),         // 生产者下标初始为0
          _data_sem(0),       // 初始化数据信号量(初始为0,表示无数据)---0
          _c_step(0)         // 消费者下标初始为0
    {
    }

    // 生产者入队方法
    void Equeue(const T &in)
    {
        // 1. 申请空位信号量(如果没有空位会阻塞)
        _blank_sem.P();
        
        {//!!!!!!!设置一个作用域,目的是lockguard出作用域时析构直接调用解锁!!!!!!!!!!
            // 使用RAII锁保护生产者临界区
            LockGuard lockguard(_pmutex);//初始化时会自动上锁,出作用域时会在析构时自动解锁
            
            // 2. 生产数据到当前生产者位置
            _rq[_p_step] = in;
            
            // 3. 更新生产者下标
            ++_p_step;
            
            // 4. 维持环形特性(下标超过容量时回到0)
            _p_step %= _cap;
        }
        
        // 释放数据信号量(表示有新数据可用)
        _data_sem.V();
    }

    // 消费者出队方法
    void Pop(T *out)
    {
        // 1. 申请数据信号量(如果没有数据会阻塞)
        _data_sem.P();
        
        {
            // 使用RAII锁保护消费者临界区
            LockGuard lockguard(_cmutex);
            
            // 2. 消费当前消费者位置的数据
            *out = _rq[_c_step];
            
            // 3. 更新消费者下标
            ++_c_step;
            
            // 4. 维持环形特性(下标超过容量时回到0)
            _c_step %= _cap;
        }
        
        // 释放空位信号量(表示有空位可用)
        _blank_sem.V();
    }

private:
    std::vector<T> _rq;  // 底层存储容器
    int _cap;            // 队列容量

    // 生产者相关成员
    Sem _blank_sem;      // 空位信号量(控制生产者)
    int _p_step;         // 生产者当前位置下标

    // 消费者相关成员
    Sem _data_sem;       // 数据信号量(控制消费者)
    int _c_step;         // 消费者当前位置下标

    // 多生产者多消费者同步锁
    Mutex _cmutex;       // 消费者互斥锁
    Mutex _pmutex;       // 生产者互斥锁
};

Main.cc:

#include <iostream>      // 标准输入输出库
#include <pthread.h>     // POSIX线程库
#include <unistd.h>      // Unix标准库(提供sleep等函数)
#include "RingQueue.hpp" // 环形队列实现

// 线程数据结构体(用于传递参数)
struct threaddata
{
    RingQueue<int> *rq;  // 指向共享的环形队列
    std::string name;    // 线程名称(用于标识)
};

// 消费者线程函数
void *consumer(void *args)
{
    // 转换参数类型
    threaddata *td = static_cast<threaddata*>(args);

    // 消费者持续运行
    while (true)
    {
        sleep(3); // 模拟消费速度较慢
        // 1. 从队列中消费数据
        int t = 0;
        td->rq->Pop(&t);

        // 2. 处理数据(此时数据已从队列移除)
        std::cout << td->name << " 消费者拿到了一个数据:  " << t << std::endl;
    }
    return nullptr;
}

// 全局生产数据(简单示例)
int data = 1;

// 生产者线程函数
void *productor(void *args)
{
    // 转换参数类型
    threaddata *td = static_cast<threaddata*>(args);
    
    // 生产者持续运行
    while (true)
    {
        sleep(1); // 模拟生产速度较快
        // 1. 生产数据前的提示信息
        std::cout << td->name << " 生产了一个任务: " << data << std::endl;

        // 2. 将数据放入队列
        td->rq->Equeue(data);

        data++; // 全局数据递增
    }
    return nullptr;
}

int main()
{
    // 创建环形队列实例(容量使用默认值)
    RingQueue<int> *rq = new RingQueue<int>();

    // 定义线程ID数组
    pthread_t c[2]; // 2个消费者线程
    pthread_t p[3]; // 3个生产者线程

    // 创建消费者线程1
    threaddata *td = new threaddata();
    td->name = "cthread-1";
    td->rq = rq;
    pthread_create(c, nullptr, consumer, td);

    // 创建消费者线程2
    threaddata *td2 = new threaddata();
    td2->name = "cthread-2";
    td2->rq = rq;
    pthread_create(c + 1, nullptr, consumer, td2);

    // 创建生产者线程3
    threaddata *td3 = new threaddata();
    td3->name = "pthread-3";
    td3->rq = rq;
    pthread_create(p, nullptr, productor, td3);

    // 创建生产者线程4
    threaddata *td4 = new threaddata();
    td4->name = "pthread-4";
    td4->rq = rq;
    pthread_create(p + 1, nullptr, productor, td4);

    // 创建生产者线程5
    threaddata *td5 = new threaddata();
    td5->name = "pthread-5";
    td5->rq = rq;
    pthread_create(p + 2, nullptr, productor, td5);

    // 等待所有线程结束(实际上不会结束)
    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(p[2], nullptr);

    // 内存释放(实际不会执行到这里)
    delete rq;
    delete td;
    delete td2;
    delete td3;
    delete td4;
    delete td5;
    return 0;
}

makefile:

ring_cp:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f ring_cp

7.线程池

1.日志

diary.hpp:

#ifndef __LOG_HPP__
#define __LOG_HPP__
// 头文件保护宏,防止重复包含

// 标准库头文件包含
#include <iostream>      // 标准输入输出流
#include <cstdio>       // C标准IO
#include <string>       // 字符串类
#include <filesystem>   // 文件系统操作(C++17)
#include <sstream>      // 字符串流
#include <fstream>      // 文件流
#include <memory>       // 智能指针
#include <ctime>        // 时间处理
#include <unistd.h>     // POSIX API(获取进程ID)
#include <mutex>        // 标准库互斥锁//这次不使用pthread.h库

namespace LogModule {
    // 全局行分隔符(Windows风格)
    const std::string gsep = "\r\n";  

    /* 日志策略基类(抽象接口) */
    class LogStrategy {
    public:
        virtual ~LogStrategy() = default;  // 虚析构函数
        // 纯虚函数:同步日志消息,下文需要重写
        virtual void SyncLog(const std::string &message) = 0;  
    };

    /* 控制台日志策略(具体实现) */
    class ConsoleLogStrategy : public LogStrategy {
    public:
        ConsoleLogStrategy() = default;  // 默认构造函数

        // 实现基类的日志同步接口
        void SyncLog(const std::string &message) override {
            // 使用lock_guard自动加锁/解锁
            std::lock_guard<std::mutex> lock(_mutex);  
            // 输出到标准输出,直接打印到显示器上
            std::cout << message << gsep;  
        }
        
        ~ConsoleLogStrategy() = default;  // 默认析构函数
    private:
        std::mutex _mutex;  // 互斥锁保证线程安全
    };

    /* 文件日志策略(具体实现) */
    const std::string defaultpath = "./log";  // 默认日志目录
    const std::string defaultfile = "my.log"; // 默认日志文件名
    
    class FileLogStrategy : public LogStrategy {
    public:
        // 构造函数,可自定义路径和文件名
        FileLogStrategy(const std::string &path = defaultpath, 
                       const std::string &file = defaultfile)
            : _path(path), _file(file) {
            std::lock_guard<std::mutex> lock(_mutex);//如果使用pthread库的pthread_mutex_lock(&_mutex),那么记得初始化pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;  
            // 检查目录是否存在                       //,还有记得要加unlock解锁,
            if (std::filesystem::exists(_path)) {  //判断路径是否存在,因为文件可以自动新建,但是路径不行
                return;
            }
            try {
                // 递归创建目录
                std::filesystem::create_directories(_path);  //创建对应的路径
            } catch (const std::filesystem::filesystem_error &e) {
                // 异常处理
                std::cerr << e.what() << '\n';  
            }
        }
        
        // 实现基类的日志同步接口
        void SyncLog(const std::string &message) override {
            std::lock_guard<std::mutex> lock(_mutex);
            
            // 构造完整文件路径,这样才能打印到文件中!!!!!!!!
            std::string filename = _path + 
                (_path.back() == '/' ? "" : "/") + _file;  
            // 以追加模式打开文件
            std::ofstream out(filename, std::ios::app);  //!!!!!!!记得使用追加!!!
            if (!out.is_open()) {
                return;  // 文件打开失败则返回
            }
            // 写入日志内容
            out << message << gsep;  
            out.close();  // 关闭文件
        }
        
        ~FileLogStrategy() = default;  // 默认析构函数
    private:
        std::string _path;   // 日志存储路径
        std::string _file;   // 日志文件名
        std::mutex _mutex;   // 互斥锁保证线程安全
    };

    /* 日志等级枚举 */
    enum class LogLevel {
        DEBUG,    // 调试信息
        INFO,     // 普通信息
        WARNING,  // 警告信息
        ERROR,    // 错误信息
        FATAL     // 致命错误
    };
    
    /* 日志等级枚举转字符串 */
    std::string Level2Str(LogLevel level) {
        switch (level) {
            case LogLevel::DEBUG: return "DEBUG";
            case LogLevel::INFO: return "INFO";
            case LogLevel::WARNING: return "WARNING";
            case LogLevel::ERROR: return "ERROR";
            case LogLevel::FATAL: return "FATAL";
            default: return "UNKNOWN";
        }
    }
    
    /* 获取当前时间戳 */
    std::string GetTimeStamp() {
        time_t curr = time(nullptr);  // 获取当前时间
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);  // 转换为本地时间
        char timebuffer[128];
        // 格式化时间为字符串
        snprintf(timebuffer, sizeof(timebuffer), 
                "%4d-%02d-%02d %02d:%02d:%02d",
                curr_tm.tm_year+1900,  // 年(从1900开始)
                curr_tm.tm_mon+1,      // 月(0-11)
                curr_tm.tm_mday,       // 日
                curr_tm.tm_hour,       // 时
                curr_tm.tm_min,        // 分
                curr_tm.tm_sec);       // 秒
        return timebuffer;
    }

    /* 日志器主类 */
    class Logger {
    public:
        Logger() {
            // 默认使用控制台输出策略
            EnableConsoleLogStrategy();  
        }
        
        // 启用文件日志策略
        void EnableFileLogStrategy() {
            std::lock_guard<std::mutex> lock(_mutex);
            // 创建文件策略实例
            _fflush_strategy = std::make_unique<FileLogStrategy>();  //类型转换
        }
        
        // 启用控制台日志策略
        void EnableConsoleLogStrategy() {
            std::lock_guard<std::mutex> lock(_mutex);
            // 创建控制台策略实例
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();  //一样的类型转换
        }

        /* 日志消息类(RAII实现) */
        class LogMessage {
        public:
            // 构造函数,收集日志元信息
            LogMessage(LogLevel level, const std::string &src_name, 
                      int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()),  // 当前时间
                  _level(level),               // 日志等级
                  _pid(getpid()),              // 进程ID
                  _src_name(src_name),         // 源文件名
                  _line_number(line_number),   // 行号
                  _logger(logger) {            // 所属日志器
                // 构造日志前缀信息
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();  // 保存前缀
            }
            
            // 重载<<操作符,支持链式调用
            template <typename T>
            LogMessage &operator<<(const T &info) {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();  // 追加日志内容
                return *this;
            }

            // 析构函数,实际执行日志写入
            ~LogMessage() {
                if (_logger._fflush_strategy) {
                    // 调用策略写入日志
                    _logger._fflush_strategy->SyncLog(_loginfo);//message析构之后会写入
                }
            }

        private:
            std::string _curr_time;   // 日志时间
            LogLevel _level;          // 日志等级
            pid_t _pid;              // 进程ID
            std::string _src_name;   // 源文件名
            int _line_number;       // 行号
            std::string _loginfo;    // 完整日志信息
            Logger &_logger;         // 所属日志器引用
        };

        // 重载()操作符创建日志消息
        LogMessage operator()(LogLevel level, const std::string &name, int line) {
            return LogMessage(level, name, line, *this);
        }
        
        ~Logger() = default;  // 默认析构函数
    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;  // 当前日志策略
        std::mutex _mutex;  // 保护策略切换的互斥锁
    };

    // 全局日志器实例
    Logger logger;

    /* 用户接口宏 */
    #define LOG(level) logger(level, __FILE__, __LINE__)  // 创建日志条目
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()  // 切换控制台输出
    #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()  // 切换文件输出
}

#endif  // __LOG_HPP__


测试:main.cc:


#include "diary.hpp"

#include <memory>

using namespace LogModule;

int main()
{
    
     //Enable_Console_Log_Strategy();
     //LOG(LogLevel::DEBUG) << "hello world" << 3.141;
     //LOG(LogLevel::DEBUG) << "hello world" << 3.142;

     //std::unique_ptr<LogStrategy> strategy = std::make_unique<ConsoleLogStrategy>(); // C++14  控制台打印
     //std::unique_ptr<LogStrategy> strategy = std::make_unique<FileLogStrategy>(); // C++14   文件打印
     //strategy->SyncLog("hello log!");
     logger(LogLevel::DEBUG, "main.cc", 10) << "hello world," << 3.14 << " " << 8899 << "aaaa";//这个是使用默认的控制台打印

     //logger(DEBUG, "main.cc", 10)  --> 创建 LogMessage 对象
    ///|
    ///v
//    多次 operator<< 调用          --> 逐步拼接字符串
    ///|
    ///v
//     LogMessage 析构             --> 调用 SyncLog
    ///|
    ///v
//策略处理(控制台/文件)       --> 加锁 -> 写入 -> 解锁



     logger(LogLevel::DEBUG, "main.cc", 10) << "hello world";
     logger(LogLevel::DEBUG, "main.cc", 10) << "hello world";
     logger(LogLevel::DEBUG, "main.cc", 10) << "hello world";
     logger(LogLevel::DEBUG, "main.cc", 10) << "hello world";
     logger(LogLevel::DEBUG, "main.cc", 10) << "hello world";
     logger(LogLevel::DEBUG, "main.cc", 10) << "hello world";
    return 0;
}

makefile:

diary:main.cc
	g++ -o $@ $^ -std=c++17 -lpthread
.PHONY:clean
clean:
	rm -f diary

9.线程池实现

Log.hpp:


#ifndef __LOG_HPP__
#define __LOG_HPP__
// 头文件保护宏,防止重复包含

// 标准库头文件包含
#include <iostream>      // 标准输入输出流
#include <cstdio>       // C标准IO
#include <string>       // 字符串类
#include <filesystem>   // 文件系统操作(C++17)
#include <sstream>      // 字符串流
#include <fstream>      // 文件流
#include <memory>       // 智能指针
#include <ctime>        // 时间处理
#include <unistd.h>     // POSIX API(获取进程ID)
#include <mutex>        // 标准库互斥锁//这次不使用pthread.h库

namespace LogModule {
    // 全局行分隔符(Windows风格)
    const std::string gsep = "\r\n";  

    /* 日志策略基类(抽象接口) */
    class LogStrategy {
    public:
        virtual ~LogStrategy() = default;  // 虚析构函数
        // 纯虚函数:同步日志消息,下文需要重写
        virtual void SyncLog(const std::string &message) = 0;  
    };

    /* 控制台日志策略(具体实现) */
    class ConsoleLogStrategy : public LogStrategy {
    public:
        ConsoleLogStrategy() = default;  // 默认构造函数

        // 实现基类的日志同步接口
        void SyncLog(const std::string &message) override {
            // 使用lock_guard自动加锁/解锁
            std::lock_guard<std::mutex> lock(_mutex);  
            // 输出到标准输出,直接打印到显示器上
            std::cout << message << gsep;  
        }
        
        ~ConsoleLogStrategy() = default;  // 默认析构函数
    private:
        std::mutex _mutex;  // 互斥锁保证线程安全
    };

    /* 文件日志策略(具体实现) */
    const std::string defaultpath = "./log";  // 默认日志目录
    const std::string defaultfile = "my.log"; // 默认日志文件名
    
    class FileLogStrategy : public LogStrategy {
    public:
        // 构造函数,可自定义路径和文件名
        FileLogStrategy(const std::string &path = defaultpath, 
                       const std::string &file = defaultfile)
            : _path(path), _file(file) {
            std::lock_guard<std::mutex> lock(_mutex);//如果使用pthread库的pthread_mutex_lock(&_mutex),那么记得初始化pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;  
            // 检查目录是否存在                       //,还有记得要加unlock解锁,
            if (std::filesystem::exists(_path)) {  //判断路径是否存在,因为文件可以自动新建,但是路径不行
                return;
            }
            try {
                // 递归创建目录
                std::filesystem::create_directories(_path);  //创建对应的路径
            } catch (const std::filesystem::filesystem_error &e) {
                // 异常处理
                std::cerr << e.what() << '\n';  
            }
        }
        
        // 实现基类的日志同步接口
        void SyncLog(const std::string &message) override {
            std::lock_guard<std::mutex> lock(_mutex);
            
            // 构造完整文件路径,这样才能打印到文件中!!!!!!!!
            std::string filename = _path + 
                (_path.back() == '/' ? "" : "/") + _file;  
            // 以追加模式打开文件
            std::ofstream out(filename, std::ios::app);  //!!!!!!!记得使用追加!!!
            if (!out.is_open()) {
                return;  // 文件打开失败则返回
            }
            // 写入日志内容
            out << message << gsep;  
            out.close();  // 关闭文件
        }
        
        ~FileLogStrategy() = default;  // 默认析构函数
    private:
        std::string _path;   // 日志存储路径
        std::string _file;   // 日志文件名
        std::mutex _mutex;   // 互斥锁保证线程安全
    };

    /* 日志等级枚举 */
    enum class LogLevel {
        DEBUG,    // 调试信息
        INFO,     // 普通信息
        WARNING,  // 警告信息
        ERROR,    // 错误信息
        FATAL     // 致命错误
    };
    
    /* 日志等级枚举转字符串 */
    std::string Level2Str(LogLevel level) {
        switch (level) {
            case LogLevel::DEBUG: return "DEBUG";
            case LogLevel::INFO: return "INFO";
            case LogLevel::WARNING: return "WARNING";
            case LogLevel::ERROR: return "ERROR";
            case LogLevel::FATAL: return "FATAL";
            default: return "UNKNOWN";
        }
    }
    
    /* 获取当前时间戳 */
    std::string GetTimeStamp() {
        time_t curr = time(nullptr);  // 获取当前时间
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);  // 转换为本地时间
        char timebuffer[128];
        // 格式化时间为字符串
        snprintf(timebuffer, sizeof(timebuffer), 
                "%4d-%02d-%02d %02d:%02d:%02d",
                curr_tm.tm_year+1900,  // 年(从1900开始)
                curr_tm.tm_mon+1,      // 月(0-11)
                curr_tm.tm_mday,       // 日
                curr_tm.tm_hour,       // 时
                curr_tm.tm_min,        // 分
                curr_tm.tm_sec);       // 秒
        return timebuffer;
    }

    /* 日志器主类 */
    class Logger {
    public:
        Logger() {
            // 默认使用控制台输出策略
            EnableConsoleLogStrategy();  
        }
        
        // 启用文件日志策略
        void EnableFileLogStrategy() {
            std::lock_guard<std::mutex> lock(_mutex);
            // 创建文件策略实例
            _fflush_strategy = std::make_unique<FileLogStrategy>();  //类型转换
        }
        
        // 启用控制台日志策略
        void EnableConsoleLogStrategy() {
            std::lock_guard<std::mutex> lock(_mutex);
            // 创建控制台策略实例
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();  //一样的类型转换
        }

        /* 日志消息类(RAII实现) */
        class LogMessage {
        public:
            // 构造函数,收集日志元信息--前缀建立
            LogMessage(LogLevel level, const std::string &src_name, 
                      int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()),  // 当前时间
                  _level(level),               // 日志等级
                  _pid(getpid()),              // 进程ID
                  _src_name(src_name),         // 源文件名
                  _line_number(line_number),   // 行号
                  _logger(logger) {            // 所属日志器
                // 构造日志前缀信息
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();  // 保存前缀
            }
            
            // 重载<<操作符,支持链式调用
            template <typename T>
            LogMessage &operator<<(const T &info) {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();  // 追加日志内容
                return *this;
            }

            // 析构函数,实际执行日志写入
            ~LogMessage() {
                if (_logger._fflush_strategy) {
                    // 调用策略写入日志
                    _logger._fflush_strategy->SyncLog(_loginfo);//message析构之后会写入
                }
            }

        private:
            std::string _curr_time;   // 日志时间
            LogLevel _level;          // 日志等级
            pid_t _pid;              // 进程ID
            std::string _src_name;   // 源文件名
            int _line_number;       // 行号
            std::string _loginfo;    // 完整日志信息
            Logger &_logger;         // 所属日志器引用
        };

        // 重载()操作符创建日志消息
        LogMessage operator()(LogLevel level, const std::string &name, int line) {
            return LogMessage(level, name, line, *this);
        }
        
        ~Logger() = default;  // 默认析构函数
    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;  // 当前日志策略
        std::mutex _mutex;  // 保护策略切换的互斥锁
    };

    // 全局日志器实例
    Logger logger;

    /* 用户接口宏 */
    #define LOG(level) logger(level, __FILE__, __LINE__)  // 创建日志条目
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()  // 切换控制台输出
    #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()  // 切换文件输出
}

#endif  // __LOG_HPP__
  • 日志和上文一致!!!!!!

Makefile:

threadpool:Main.cc
	g++ -o $@ $^ -std=c++17 -lpthread
.PHONY:clean
clean:
	rm -f threadpool

Task.hpp:


#pragma once
#include <iostream>
#include <unistd.h>
#include <functional>
#include "Log.hpp"

using namespace LogModule;

// 任务形式2
// 我们定义了一个任务类型,返回值void,参数为空
using task_t = std::function<void()>;

void Download()
{
   LOG(LogLevel::DEBUG) << "我是一个下载任务...";
}


  • 这个task_t就是到时候要传递给线程的任务函数!!!!

Thread.hpp:

#ifndef _THREAD_H_       // 防止头文件重复包含的宏定义
#define _THREAD_H_       // 定义头文件标识符

#include <iostream>      // 标准输入输出流
#include <string>       // 字符串操作
#include <pthread.h>    // POSIX线程库
#include <cstdio>       // C标准IO
#include <cstring>      // C字符串操作
#include <functional>   // 函数对象包装器
#include "Log.hpp"      // 日志系统头文件

namespace ThreadModlue   // 线程模块命名空间
{
    using namespace LogModule;  // 使用日志模块命名空间
    static uint32_t number = 1; // 线程计数器(注意:多线程下非原子操作,有并发问题),纪录由多少个线程

    class Thread
    {
        using func_t = std::function<void()>; // 定义线程任务类型-----即线程建立后要做什么!!!!(无参无返回值的可调用对象)

    private:
        // 设置线程为分离状态
        void EnableDetach()
        {
            _isdetach = true;
        }

        // 标记线程为运行状态
        void EnableRunning()
        {
            _isrunning = true;
        }

        // 静态线程入口函数(必须静态,因为pthread_create需要C风格函数)
        static void *Routine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);            // 转换参数为Thread对象指针
            self->EnableRunning();                                 // 标记线程运行状态
            if (self->_isdetach)                                   // 如果设置了分离属性
                self->Detach();                                    // 执行线程分离
            pthread_setname_np(self->_tid, self->_name.c_str());   // 设置线程名称------在内核中改名!!!!!!!!!!!!!!!!!!不是在我们创建的类的——name中改
            self->_func();                                         // 执行用户任务函数
            return nullptr;                                        // 线程退出
        }

    public:
        // 构造函数:初始化线程属性
        Thread(func_t func)
            : _tid(0),                 // 初始化线程ID为0
              _isdetach(false),        // 默认非分离状态
              _isrunning(false),       // 默认非运行状态
              res(nullptr),            // 线程返回值指针初始化为空
              _func(func)              // 保存用户任务函数
        {
            _name = "thread-" + std::to_string(number++);  // 生成线程名称(如thread-1)number是域内的全局变量
        }

        // 设置线程为分离状态
        void Detach()
        {
            if (_isdetach)            // 如果已经是分离状态则直接返回
                return;
            if (_isrunning)           // 如果线程正在运行
                pthread_detach(_tid); // 调用系统分离函数---------pthread_detach
            EnableDetach();           // 更新状态标记
        }

        // 获取线程名称
        std::string Name()
        {
            return _name;
        }

        // 启动线程
        bool Start()
        {
            if (_isrunning)           // 防止重复启动
                return false;
            int n = pthread_create(&_tid, nullptr, Routine, this);  // 创建线程---创建成功后直接走routine函数!!!
            if (n != 0)              // 创建失败处理
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        // 停止线程(强制取消)
        bool Stop()
        {
            if (_isrunning)          // 只有运行中的线程才能停止
            {
                int n = pthread_cancel(_tid);  // 发送取消请求
                if (n != 0)         // 取消失败处理
                {
                    return false;
                }
                else
                {
                    _isrunning = false;  // 更新状态标记
                    return true;
                }
            }
            return false;           // 未运行的线程直接返回失败
        }

        // 等待线程结束
        void Join()
        {
            if (_isdetach)         // 分离线程不可join
            {
                return;
            }
            int n = pthread_join(_tid, &res);  // 阻塞等待线程结束
            if (n != 0)           // 失败日志
            {
                LOG(LogLevel::DEBUG) << "Join线程失败";
            }
            else
            {
                LOG(LogLevel::DEBUG) << "Join线程成功";
            }
        }

        // 析构函数(空实现,资源需用户显式管理)
        ~Thread()
        {
        }

    private:
        pthread_t _tid;           // 线程ID
        std::string _name;        // 线程名称
        bool _isdetach;           // 是否分离状态标记
        bool _isrunning;          // 是否运行中标记
        void *res;                // 线程返回值指针
        func_t _func;             // 用户任务函数对象
    };
}

#endif // _THREAD_H_              // 结束头文件保护
  • 封装线程的等待,创建,命名(内核),停止(结束)和入口函数

ThreadPool.hpp:

#pragma once  // 防止头文件重复包含
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <pthread.h>  // POSIX线程库
#include "Log.hpp"    // 日志系统
#include "Thread.hpp" // 线程封装类

// .hpp header only  // 提示这是头文件-only的实现

namespace ThreadPoolModule  // 线程池模块命名空间
{
    using namespace ThreadModlue;  // 使用线程模块
    using namespace LogModule;     // 使用日志模块

    static const int gnum = 5;  // 默认线程数量
    
    template <typename T>  // 模板类,T表示任务类型
    class ThreadPool
    {
    private:
        // 唤醒所有休眠线程
        void WakeUpAllThread()
        {
            pthread_mutex_lock(&_mutex);  // 加锁
            if (_sleepernum)              // 如果有休眠线程-------—_sleepernum是指休眠的线程个数
                pthread_cond_broadcast(&_cond);  // pthread_cond_broadcast广播唤醒所有等待条件变量的线程,---------
            pthread_mutex_unlock(&_mutex);  // 解锁
            LOG(LogLevel::INFO) << "唤醒所有的休眠线程";  // 记录日志
        }

        // 唤醒单个休眠线程
        void WakeUpOne()
        {
            pthread_cond_signal(&_cond);  // pthread_cond_signal发送信号唤醒一个等待线程
            LOG(LogLevel::INFO) << "唤醒一个休眠线程";  // 记录日志
        }

        // 构造函数
        ThreadPool(int num = gnum) : _num(num), _isrunning(false), _sleepernum(0)//num是要建立的线程个数,初始设置没有线程在运行,也没有休眠,因为一开始没建立线程!!!
        {
            pthread_mutex_init(&_mutex, nullptr);  // 初始化互斥锁
            pthread_cond_init(&_cond, nullptr);    // 初始化条件变量
            
            // 创建线程池中的工作线程
            for (int i = 0; i < num; i++)
            {
                _threads.emplace_back(  // 向vector中添加Thread对象--------emplace_back(Args&&... args)函数会直接构造对象,(Args&&... args)是参数,emplace_back 在构造 std::vector<Thread> 中的元素时,会直接调用 Thread 类的构造函数。
                    [this]()  //lambda表达式作为线程函数------
                    {
                        HandlerTask();  // 线程执行的任务处理函数----[this](){ HandlerTask(); }-----//1.[this] 这里的 this 是指向当前 ThreadPool 对象的指针。因为HandlerTask() 是 ThreadPool 类的成员方法,必须通过 this 指针调用。
                    });                 //2.()表示声明 lambda 的参数列表(此处为空,因为不需要参数)。适配 Thread 类构造函数所需的 std::function<void()> 类型
            }                           //3.{ HandlerTask(); }  定义 lambda 的实际行为:调用 ThreadPool::HandlerTask() 方法。
                                        //4.thread的函数变量是这个lambda表达式,不是HandlerTask()这个函数,但thread调用lambda表达式时会直接调用this->HandlerTask()
        }
        
        // 启动线程池
        void Start()
        {
            if (_isrunning)  // 如果已经在运行则返回
                return;
            _isrunning = true;  // 设置运行标志
            // 启动所有线程
            for (auto &thread : _threads)
            {
                thread.Start();  // 启动线程
                LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();  // 记录日志
            }
        }

        // 禁止拷贝构造和赋值
        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

    public:
        // 获取单例实例
        static ThreadPool<T> *GetInstance()   //----------确保整个程序中只有一个 ThreadPool 实例,并提供全局访问点
        {
            if (inc == nullptr)  // 第一次检查
            {
                pthread_mutex_lock(&_lock);  // 加锁

                LOG(LogLevel::DEBUG) << "获取单例....";
                if (inc == nullptr)  // 第二次检查(双检锁)
                {
                    LOG(LogLevel::DEBUG) << "首次使用单例, 创建之....";
                    inc = new ThreadPool<T>();  // 创建实例
                    inc->Start();  // 启动线程池
                }
                pthread_mutex_unlock(&_lock);  // 解锁
            }

            return inc;  // 返回单例
        }
        
        // 停止线程池
        void Stop()
        {
            if (!_isrunning)  // 如果已经停止则返回
                return;
            _isrunning = false;  // 设置停止标志

            // 唤醒所有的线程
            WakeUpAllThread();
        }
        
        // 等待所有线程结束
        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();  // 等待每个线程结束
            }
        }
        
        // 任务处理函数(工作线程执行)
        void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));  // 获取线程名称
            while (true)  // 工作循环
            {
                T t;  // 任务对象
                {
                    pthread_mutex_lock(&_mutex);  // 加锁
                    // 等待条件:1.队列为空 2.线程池在运行
                    while (_taskq.empty() && _isrunning)
                    {
                        _sleepernum++;  // 增加休眠线程计数
                        pthread_cond_wait(&_cond, &_mutex);  // 等待条件变量
                        _sleepernum--;  // 减少休眠线程计数
                    }
                    // 检查是否应该退出
                    if (!_isrunning && _taskq.empty())
                    {
                        pthread_mutex_unlock(&_mutex);  // 解锁
                        LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;  // 退出循环
                    }

                    // 获取任务
                    t = _taskq.front();  // 从队列头部获取任务
                    _taskq.pop();        // 移除队列头部任务
                    pthread_mutex_unlock(&_mutex);  // 解锁
                }
                t(); // 执行任务(在锁外执行)--------线程被唤醒后拿到download()函数并运行!!!!
            }
        }
        
        // 向任务队列添加任务
        bool Enqueue(const T &in)
        {
            if (_isrunning)  // 只有运行状态才能添加任务
            {
                pthread_mutex_lock(&_mutex);  // 加锁
                _taskq.push(in);  // 添加任务到队列
                // 如果所有线程都在休眠,则唤醒一个
                if (_threads.size() == _sleepernum)
                    WakeUpOne();
                pthread_mutex_unlock(&_mutex);  // 解锁
                return true;
            }
            return false;  // 线程池未运行,添加失败
        }
        
        // 析构函数
        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mutex);  // 销毁互斥锁
            pthread_cond_destroy(&_cond);    // 销毁条件变量
        }

    private:
        std::vector<Thread> _threads;  // 工作线程集合
        int _num;                      // 线程数量
        std::queue<T> _taskq;          // 任务队列
        pthread_cond_t _cond;          // 条件变量
        pthread_mutex_t _mutex;        // 互斥锁

        bool _isrunning;               // 线程池运行状态
        int _sleepernum;               // 当前休眠线程数

        static ThreadPool<T> *inc;      // 单例指针---保证只有一个线程池
        static pthread_mutex_t _lock;   // 单例锁
    };

    // 静态成员初始化----要在类外面进行初始化
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr;

    template <typename T>
    pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;  // 静态锁初始化

}

Main.cc:

#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include <memory>

using namespace LogModule;
using namespace ThreadPoolModule;

int main()
{
    Enable_Console_Log_Strategy();
    // ThreadPool<task_t> tp;
    // ThreadPool<task_t> tp1 = tp;
    // ThreadPool<task_t> *tp = new ThreadPool<task_t>();

    // tp->Start();
    // 有一个单例了! 如果线程池本身,会被多线程获取呢??
    int count = 10;
    while (count)
    {
        sleep(1);
        ThreadPool<task_t>::GetInstance()->Enqueue(Download);//ThreadPool<task_t>::GetInstance() 的调用并不依赖于实例化的对象,而是通过 静态成员函数 和 模板类的特性 实现的。
        count--;                                             //且threadpool<T>*inc 是静态stastic变量,所以可以不用实例化直接获取,一定是唯一的!!!!!!!
    }

    ThreadPool<task_t>::GetInstance()->Stop();
    ThreadPool<task_t>::GetInstance()->Join();
    return 0;
}

在这里插入图片描述


线程池运行流程与输出结果分析

代码概览

int main() {
    Enable_Console_Log_Strategy();
    int count = 10;
    while (count--) {
        sleep(1);
        ThreadPool<task_t>::GetInstance()->Enqueue(Download);
    }
    ThreadPool<task_t>::GetInstance()->Stop();
    ThreadPool<task_t>::GetInstance()->Join();
    return 0;
}

完整运行流程

阶段1:线程池初始化

步骤函数调用栈关键操作输出日志
1main()GetInstance()首次调用单例获取[DEBUG] 获取单例....
2ThreadPool<task_t>()构造线程池实例[DEBUG] 首次使用单例,创建之....
3Start()启动5个工作线程[INFO] start new thread success: thread-1
[INFO] start new thread success: thread-2

[INFO] start new thread success: thread-5

阶段2:任务提交与处理(循环10次)

步骤函数调用栈关键操作输出日志
1Enqueue(Download)任务入队并唤醒线程[INFO] 唤醒一个休眠线程
2HandlerTask()工作线程获取任务[INFO] thread-[X] 处理下载任务...
3Download()执行具体任务(假设Download函数输出)

注:X为线程ID(1-5),任务处理顺序可能随机

阶段3:线程池关闭

步骤函数调用栈关键操作输出日志
1Stop()设置停止标志并唤醒所有线程[INFO] 唤醒所有的休眠线程
2HandlerTask()线程检查退出条件[INFO] thread-1 退出了,线程池退出&&任务队列为空

[INFO] thread-5 退出了,线程池退出&&任务队列为空
3Join()等待线程结束[DEBUG] Join线程成功

完整输出示例

[DEBUG] 获取单例....
[DEBUG] 首次使用单例,创建之....
[INFO] start new thread success: thread-1
[INFO] start new thread success: thread-2
[INFO] start new thread success: thread-3
[INFO] start new thread success: thread-4
[INFO] start new thread success: thread-5
[INFO] 唤醒一个休眠线程
[INFO] thread-1 处理下载任务...
[INFO] 唤醒一个休眠线程
[INFO] thread-2 处理下载任务...
(...共10次任务提交...)
[INFO] 唤醒所有的休眠线程
[INFO] thread-1 退出了,线程池退出&&任务队列为空
[INFO] thread-2 退出了,线程池退出&&任务队列为空
[INFO] thread-3 退出了,线程池退出&&任务队列为空
[INFO] thread-4 退出了,线程池退出&&任务队列为空
[INFO] thread-5 退出了,线程池退出&&任务队列为空
[DEBUG] Join线程成功

关键时序图

主线程线程池单例工作线程DownloadGetInstance()初始化5个线程Enqueue(Download)唤醒线程取任务执行任务loop[10次任务]Stop()广播唤醒退出循环Join()主线程线程池单例工作线程Download

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值