从0-1手写线程池(C++)

一、C++线程库的基本使用

进程:运行中的程序,线程:进程中的进程(进程中的一个实例);

1.1、创建,使用

void printHello() {
    std::cout << "Hello from thread!\n";
    cout << "函数执行结束" << endl;
}
int main()
{
    // 创建一个线程,执行 printHello 函数
    thread t(printHello);
    //t.join(); // 等待线程完成(检查t线程有没有结束)
    //分离线程
    //t.detach(); // 分离线程,允许线程在后台运行,不需要等待它完成
    //joinable() 检查线程是否可以 join--->阻塞
    if (t.joinable()) {
        t.join(); // 等待线程完成
    }
    std::cout << "Hello World!\n";
}

二、线程函数中的数据未定义错误

2.1、传递临时变量的问题

void foo(int& x)
{
    x += 1;
}
int main()
{
    int a = 1;
    thread t1(foo,a); // 创建一个线程,传递引用参数=》1是临时变量,而临时值在表达式结束之后就会被销毁,这就会让线程访问到无效的引用。
    t1.join(); // 等待线程完成
    return 0;
}

void foo(int& x)
{
    x += 1;
}
int main()
{
    int a = 1;
    thread t1(foo,ref(a)); // 创建一个线程,传递引用参数=》1是临时变量,而临时值在表达式结束之后就会被销毁,这就会让线程访问到无效的引用。
    t1.join(); // 等待线程完成
    return 0;
}

2.2、传递指针或引用指向局部变量的问题

void foo(int& x)
{
    x += 1;
}
void test()
{
    int a = 11;
    t = thread(foo, ref(a)); // 创建一个线程,传递引用参数
}
int main()
{
    test(); // 调用 test 函数,创建线程
    t.join(); // 等待线程完成     线程对象的生命周期与函数作用域有关
    return 0;
}
//修改
void foo(int& x)
{
    x += 1;
}
int a  =11;
void test()
{
    //int a = 11;
    t = thread(foo, ref(a)); // 创建一个线程,传递引用参数
}
int main()
{
    test(); // 调用 test 函数,创建线程
    t.join(); // 等待线程完成     线程对象的生命周期与函数作用域有关
    return 0;
}

#include <iostream>
#include <thread>
void foo(int* ptr) {
    std::cout << *ptr << std::endl; // 访问已经被销毁的指针
}
int main() {
    int x = 1;
    std::thread t(foo, &x); // 传递指向局部变量的指针
    t.join();
    return 0;
}
//在这个例子中,我们定义了一个名为`foo`的函数,它接受一个整型指针作为参数,并输出该指针所指向的整数值。然后,我们创建了一个名为`t`的线程,将`foo`函数以及指向局部变量`x`的指针作为参数传递给它。这样会导致在线程函数执行时,指向局部变量`x`的指针已经被销毁,从而导致未定义行为。
//修改
void foo(int* ptr) {
    std::cout << *ptr << std::endl;
    delete ptr; // 在使用完指针后,需要手动释放内存
}
int main() {
    int* ptr = new int(1); // 在堆上分配一个整数变量
    std::thread t(foo, ptr); // 将指针传递给线程
    t.join();
    return 0;
}
2.3、传递指针或引用指向已经释放的内存的问题
#include <iostream>
#include <thread>
void foo(int* ptr) {
    std::cout << *ptr << std::endl; // 访问已经被销毁的指针
}
int main() {
    int *ptr = new int(1);
    std::thread t(foo, ptr);
    delete ptr;//出现野指针
    return 0;
}
​

2.4、类成员函数作为入口函数,类对象被提前释放

#include <iostream>
#include <thread>
​
class MyClass {
public:
    void func() {
        std::cout << "Thread " << std::this_thread::get_id() 
        << " started" << std::endl;
        // do some work
        std::cout << "Thread " << std::this_thread::get_id() 
        << " finished" << std::endl;
    }
};
​
int main() {
    MyClass obj;
    std::thread t(&MyClass::func, &obj);
    // obj 被提前销毁了,会导致未定义的行为
    return 0;
}//或者在t(&MyClass::func, &obj);之后添加t.join()分离线程
class A {
public:
    void foo()
    {
        cout << "A::foo() called" << endl;
    }
};
int main()
{
    shared_ptr<A> a = make_shared<A>(); // 使用智能指针创建 A 类的实例
    thread t1(&A::foo, a); // 创建一个线程,调用 A 类的成员函数
    t1.join(); // 等待线程完成
    return 0;
}

2.5、入口函数为类的私有成员函数

class MyClass {
private:
    friend void myThreadFunc(MyClass* obj);
    void privateFunc(){
    std::cout << "Thread " 
    << std::this_thread::get_id() << " privateFunc" << std::endl;
}
};
​
void myThreadFunc(MyClass* obj) {
    obj->privateFunc();
}   
​
int main() {
    MyClass obj;
    std::thread thread_1(myThreadFunc, &obj);
    thread_1.join();
    return 0;
}//使用友元
class A {
private:
    friend void thread_foo(); // 声明友元函数
    void foo()
    {
        cout << "A::foo() called" << endl;
    }
};
void thread_foo()
{
    shared_ptr<A> a = make_shared<A>(); // 使用智能指针创建 A 类的实例
    thread t1(&A::foo, a); // 创建一个线程,调用 A 类的成员函数
    t1.join(); // 等待线程完成
}

三、互斥量解决多线程数据共享问题

// 互斥量解决多线程数据共享问题.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
int add(int &a)
{
    mtx.lock();
    a += 1;
    mtx.unlock();
    return a;
}
int main()
{
    int a = 0;
    thread t1(&add, std::ref(a));
    thread t2(&add, std::ref(a));
    t1.join();
    t2.join();
    return 0;
}

四、互斥量死锁

void func_1()
{
    m1.lock();
    m2.lock();
    m1.unlock();
    m2.unlock();    
}
void func_2()
{
    m2.lock();
    m1.lock();
    m1.unlock();
    m2.unlock();
}

五、lock_guard与unique_lock

5.1、lock_guard

std::lock_guard 是 C++ 标准库中的一种互斥量封装类,用于保护共享数据,防止多个线程同时访问同一资源而导致的数据竞争问题。

std::lock_guard 的特点如下:

  • 当构造函数被调用时,该互斥量会被自动锁定。

  • 当析构函数被调用时,该互斥量会被自动解锁。

  • std::lock_guard 对象不能复制或移动,因此它只能在局部作用域中使用。

  • //void func1()
    //{
    //  for (int i = 0; i < 1000000; i++)
    //  {
    //      lock_guard<mutex> lg(mtx);
    //      shared_data++;
    //  }
    //}

5.2、std::unique_lock

std::unique_lock 是 C++ 标准库中提供的一个互斥量封装类,用于在多线程程序中对互斥量进行加锁和解锁操作。它的主要特点是可以对互斥量进行更加灵活的管理,包括延迟加锁、条件变量、超时等。

std::unique_lock 提供了以下几个成员函数:

  • lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。

  • try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 true

  • try_lock_for(const std::chrono::duration<Rep, Period>& rel_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。

  • try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。

  • unlock():对互斥量进行解锁操作。

除了上述成员函数外,std::unique_lock 还提供了以下几个构造函数:

  • unique_lock() noexcept = default:默认构造函数,创建一个未关联任何互斥量的 std::unique_lock 对象。

  • explicit unique_lock(mutex_type& m):构造函数,使用给定的互斥量 m 进行初始化,并对该互斥量进行加锁操作。

  • unique_lock(mutex_type& m, defer_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,但不对该互斥量进行加锁操作。

  • unique_lock(mutex_type& m, try_to_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,并尝试对该互斥量进行加锁操作。如果加锁失败,则创建的 std::unique_lock 对象不与任何互斥量关联。

  • unique_lock(mutex_type& m, adopt_lock_t) noexcept:构造函数,使用给定的互斥量 m 进行初始化,并假设该互斥量已经被当前线程成功加锁。

void func1()
{
    for (int i = 0; i < 1000000; i++)
    {
        unique_lock<mutex> lg(mtx, defer_lock);//延迟锁定{构造但是不加锁}
        lg.lock(); // 显式加锁
        shared_data++;
        lg.unlock(); // 显式解锁
    }
}

六、call_once与其使用场景

单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑线程安全的问题。

(实现有问题)

class Log;
static Log* log = nullptr;  
once_flag onceFlag; // 用于确保初始化只执行一次
class Log {  
public:  
   Log() {}  
   Log(const Log& log) = delete; // 禁止拷贝构造  
   Log& operator=(const Log& log) = delete; // 禁止拷贝赋值  
​
   static Log& GetInstance() {  
       call_once(onceFlag, init);
       return *log; // 返回单例对象  
   }  
​
   static void init() { // 初始化单例对象  
       if(!log) log = new Log;  
   }  
​
   void PrintLog(string message) {  
       cout << __TIME__ << ":" << message << endl;  
   } 
};  
​
void prinrError() {  
   Log::GetInstance().PrintLog("error");  
}  
​
int main() {  
   thread t1(prinrError);  
   thread t2(prinrError);  
   t1.join();  
   t2.join(); // t1 和 t2 都会调用 prinrError 函数,但单例对象只会被创建一次  
}

call_once解决多线程中单例模式,对象同时创建的问题。

七、condition_variable与其使用场景

生产者-消费者模型

std::condition_variable 的步骤如下:

  1. 创建一个 std::condition_variable 对象。

  2. 创建一个互斥锁 std::mutex 对象,用来保护共享资源的访问。

  3. 在需要等待条件变量的地方

    使用 std::unique_lock<std::mutex> 对象锁定互斥锁

    并调用 std::condition_variable::wait()std::condition_variable::wait_for()std::condition_variable::wait_until() 函数等待条件变量。

  4. 在其他线程中需要通知等待的线程时,调用 std::condition_variable::notify_one()std::condition_variable::notify_all() 函数通知等待的线程。

// condition_variable与其使用场景.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
​
#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <string>
using namespace std;
queue<int> g_queue;
condition_variable g_cv;
mutex mtx;
void Producer()
{
    for (int i = 0; i < 10; ++i)
    {
        unique_lock<mutex> lock(mtx);
        //如果队列满了,则等待
        g_cv.notify_one();
        g_queue.push(i);
        cout << "Producer:" << i << endl;
    }
    this_thread::sleep_for(chrono::microseconds(100));
}
void Consumer()
{
    while (1)
    {
        unique_lock<mutex> lock(mtx);
        bool isEmpty = g_queue.empty();
        //如果为空,则等待
        g_cv.wait(lock, []() {return !g_queue.empty();});
        int value = g_queue.front();
        g_queue.pop();
        cout << "Consumer:" << value << endl;
    }
}
int main()
{
    thread t1(Producer);
    thread t2(Consumer);
    t1.join();
    t2.join();
}

​
​
​

八、C++实现跨平台线程池

重新学习

#include <iostream>
#include <thread>
#include <queue>
#include <vector>
#include <functional>
#include <condition_variable>
#include <atomic>
#include <mutex>
#include <chrono>
using namespace std;
// 跨平台线程池实现
class ThreadPool {
public:
    ThreadPool(int numThreads):stop(false)
    {
        for (int i = 0;i < numThreads;i++)
        {
            threads.emplace_back([this]() {
                while (1) {
                    unique_lock<mutex> lock(mtx); // 使用unique_lock代替lock_guard以便于手动解锁
                    condition.wait(lock, [this]() { return !tasks.empty() || stop; }); // 等待任务或停止信号
                    if (stop && tasks.empty()) return; // 如果停止且任务队列为空,则退出线程
                    function<void()> task = move(tasks.front()); // 获取任务
                    tasks.pop(); // 移除任务
                    //lock.unlock(); // 手动解锁,允许其他线程访问任务队列
                    task(); // 执行任务
                }
                });
        }
    }
    template<class F, class... Args>
    void enqueue(F&& f, Args&&... args)//&&万能引用,允许传递任意类型的可调用对象和参数
    {
        function<void()> task = bind(forward<F>(f), forward<Args>(args)...); // 绑定任务   C++111完美转发,允许传递任意类型的可调用对象和参数
        {
            unique_lock<mutex> lock(mtx); // 使用unique_lock代替lock_guard以便于手动解锁
            tasks.emplace(move(task)); // 将任务添加到队列
        }
        condition.notify_one(); // 通知一个等待线程
    }
    ~ThreadPool()
    {
        {
            unique_lock<mutex> lock(mtx);
            stop = true; // 设置停止标志
        }
        condition.notify_all(); // 通知所有线程
        for (auto &t : threads) {
        
                t.join(); // 等待线程结束
        }
    }
    
private:
    vector<thread> threads; // 修复:定义线程数组
    queue<function<void()>> tasks; // 任务队列
    mutex mtx;
    condition_variable condition;
    bool stop;
​
};
int main()
{
    ThreadPool pool(4); // 创建一个包含4个线程的线程池
    // 向线程池添加任务
    for (int i = 0; i < 10; ++i) {
        pool.enqueue([i]() {
            cout << "Task " << i << " is running;" << endl;
            this_thread::sleep_for(chrono::seconds(1)); // 模拟任务执行时间
            cout << "Task " << i << " completed." << endl;
            });
    }
    return 0;
}

九、异步并发

1、async

// 异步并发.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
​
#include <iostream>
#include <future>
using namespace std;
int func() {
    int i = 0;
    for (int i = 0;i < 100;i++)
    {
        i++;
    }
    return i;
}
void func1(promise<int> f)
{
    f.set_value(1000); // 设置 promise 的值
    
}
int main()
{
    /*packaged_task<int()> task(func); // 创建一个打包任务
    auto featured_result = task.get_future(); // 获取未来对象
    std::thread t1(std::move(task)); // 将打包任务移动到线程中执行
    cout << featured_result.get() << endl; // 获取异步函数的返回值
    future<int> future_result = async(launch::async, func);//不阻塞继续往下运行
    // 这里可以执行其他操作
    cout << func() << endl;
    cout << future_result.get() << endl; // 获取异步函数的返回值*/
    //异步编程
    promise<int> f;// 创建一个 promise 对象
    auto future_result = f.get_future(); // 获取与 promise 关联的 future 对象
    std::thread t1(func1, move(f)); // 将 promise 对象传递给线程
    t1.join(); // 等待线程完成
    cout << future_result.get() << endl; // 获取异步函数的返回值
    return 0;
}

2、future

十、原子操作atomic

#include <iostream>
#include <future>
using namespace std;
int func() {
    int i = 0;
    for (int i = 0;i < 100;i++)
    {
        i++;
    }
    return i;
}
void func1(promise<int> f)
{
    f.set_value(1000); // 设置 promise 的值
    
}
int main()
{
    /*packaged_task<int()> task(func); // 创建一个打包任务
    auto featured_result = task.get_future(); // 获取未来对象
    std::thread t1(std::move(task)); // 将打包任务移动到线程中执行
    cout << featured_result.get() << endl; // 获取异步函数的返回值
    future<int> future_result = async(launch::async, func);//不阻塞继续往下运行
    // 这里可以执行其他操作
    cout << func() << endl;
    cout << future_result.get() << endl; // 获取异步函数的返回值*/
    //异步编程
    promise<int> f;// 创建一个 promise 对象
    auto future_result = f.get_future(); // 获取与 promise 关联的 future 对象
    std::thread t1(func1, move(f)); // 将 promise 对象传递给线程
    t1.join(); // 等待线程完成
    cout << future_result.get() << endl; // 获取异步函数的返回值
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值