C++ 多线程相关

C++ 多线程相关

std::thread

/*******************************************************************************************
std::thread 
如果没有调用 join 或者 detach 函数,假如线程函数执行时间较长,此时线程对象的生命周期结束调用析构函数清理资源,
这时可能会发生错误,这里有两种解决办法,一个是调用 join(),保证线程函数的生命周期和线程对象的生命周期相同,
另一个是调用 detach(),将线程和线程对象分离,
这里需要注意,如果线程已经和对象分离,那我们就再也无法控制线程什么时候结束了,不能再通过 join 来等待线程执行完。 
*******************************************************************************************/

#include <iostream>
#include <thread>
using namespace std;

int main() {
    auto func1 = []() {
        for (int i = 0; i < 10; i++) {
            cout << i << " ";
        }
        cout << "当前线程ID " << std::this_thread::get_id() << endl;
    };
    std::thread t1(func1);
    if (t1.joinable()) {
        t1.detach();
    }

    auto func2 = [](int k) {
        for (int i = 0; i < k; i++) {
            cout << i << " ";
        }
        cout << endl;
    };
    std::thread t2(func2, 20);
    if (t2.joinable()) {
        t2.join();
    }

    // c++11 还提供了获取线程 id,或者系统 cpu 个数
    cout << "当前 CPU 个数 " << std::thread::hardware_concurrency() << endl;
    
    // 获取 thread native_handle,使得线程休眠等功能  handle 可用于 pthread 相关操作
    auto handle = t1.native_handle();
    std::this_thread::sleep_for(std::chrono::seconds(1));

    return 0;
}

// 编译:g++ thread_commen.cpp -std=c++11 -lpthread
/*****************************************************************
std::thread 
对 thread 进行封装,避免没有调用 join 或者 detach 可导致程序出错的情况出现
*******************************************************************/
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;

class ThreadGuard {
public:
    enum class DesAction { join, detach };

    ThreadGuard(std::thread&& t, DesAction a) : 
        t_(std::move(t)), action_(a) {}
    
    ~ThreadGuard() {
        if (t_.joinable()) {
            if (action_ == DesAction::join) {
                t_.join();
            } else {
                t_.detach();
            } 
        }
    }
    ThreadGuard(ThreadGuard&&) = default;
    ThreadGuard& operator=(ThreadGuard&&) = default;

    std::thread& get() { return t_; }

private:
    std::thread t_;
    DesAction action_;
};

int main() {
    ThreadGuard t(std::thread([]() {
        for (int i = 0; i < 10; i++) {
            cout << "thread guard " << i << " ";
        }
        cout << endl;
    }), ThreadGuard::DesAction::join);

    return 0;
}   

std::mutex 和 std::timed_mutex

/***********************************************************
std::mutex 是一种线程同步的手段,用于保存多线程同时操作的共享数据。

 *  std::mutex 分为四种:
 *  1 std::mutex:独占的互斥量,不能递归使用,不带超时功能
 *  2 std::recursive_mutex:递归互斥量,可重入,不带超时功能
 *  3 std::timed_mutex:带超时的互斥量,不能递归
 *  4 std::recursive_timed_mutex:带超时的互斥量,可以递归使用
***********************************************************/

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;

std::mutex mutex_;

int main() {
    auto func1 = [](int k) {
        mutex_.lock();
        for (int i = 0; i < k; ++i) {
            cout << i << " ";
        }
        cout << endl;
        mutex_.unlock();
    };

    std::thread threads[5];
    for (int i = 0; i < 5; ++i) {
        threads[i] = std::thread(func1, 200);
    }

    for (auto& th : threads) {
        if (th.joinable()) {
            th.join();
        }
    }

    return 0;
}
/* std::timed_mutex:*/
#include <iostream>
#include <mutex>
#include <thread>
#include <chrono>
using namespace std;

std::timed_mutex timed_mutex_;

int main() {
    auto func1 = [](int k) {
        timed_mutex_.try_lock_for(std::chrono::milliseconds(200));
        for (int i = 0; i < k; ++i) {
            cout << i << " ";
        }
        cout << endl;
        timed_mutex_.unlock();
    };

    std::thread threads[5];
    for (int i = 0; i < 5; ++i) {
        threads[i] = std::thread(func1, 200);
    }

    for (auto& th : threads) {
        if (th.joinable()) {
            th.join();
        }
    }

    return 0;
}

std::lock_guard 和 std::unique_lock

/* std::lock_guard 和 std::unique_lock
 * std::lock_gurad 相比于 std::unique_lock 更加轻量级,少了一些成员函数,std::unique_lock 类有 unlock 函数,可以手动释放锁,
 * 所以条件变量都配合 std::unique_lock 使用,而不是 std::lock_guard,因为条件变量在 wait 时需要有手动释放锁的能力
 */
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;

std::mutex mutex_;

int main() {
    auto func1 = [](int k) {
        // std::lock_guard<std::mutex> lock(mutex_);
        std::unique_lock<std::mutex> lock(mutex_);
        for (int i = 0; i < k; ++i) {
            cout << i << " ";
        }
        cout << endl;
    };

    std::thread threads[5];
    for (int i = 0; i < 5; ++i) {
        threads[i] = std::thread(func1, 200);
    }

    for (auto& th : threads) {
        if (th.joinable()) {
            th.join();
        }
    }

    return 0;
}

std::atomic

/*****************************
std::atomic<T> 原子变量 
******************************/

//计数器
#include <iostream>
#include <atomic>
#include <thread>
#include <mutex>
using namespace std;

// 普通计数器
struct OriginCounter {
    int count = 0;
    std::mutex mutex_;
    
    void add() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++count;
    }

    void sub() {
        std::lock_guard<std::mutex> lock(mutex_);
        --count;
    }

    int get() {
        std::lock_guard<std::mutex> lock(mutex_);
        return count;
    }
};

// 普通计数器
struct NewCounter {
    std::atomic<int> count{0};
    
    void add() {
        ++count;
        // count.store(++count); 这种方式也可以
    }

    void sub() {
        --count;
        // count.store(--count); 这种方式也可以
    }

    int get() {
        return count.load();
    }
};

int main() {
    OriginCounter originCounter;
    NewCounter newCounter;

    // 创建多个线程来增加和减少计数
    std::thread t1([&]() {
        for (int i = 0; i < 10000; ++i) {
            originCounter.add();
            newCounter.add();
        }
    });

    std::thread t2([&]() {
        for (int i = 0; i < 10000; ++i) {
            originCounter.sub();
            newCounter.sub();
        }
    });

    // 等待线程结束
    t1.join();
    t2.join();

    // 获取结果
    cout << "originCounter: " << originCounter.get() << endl;
    cout << "newCounter: " << newCounter.get() << endl;

    return 0;
}

/* 输出结果:
 * originCounter: 0
 * newCounter: 0
 */

std::call_once

/****************************************************************
C++11 提供 std::call_once 用来保证某一函数在多线程环境下只被调用一次
std::call_once 需要配合 std::once_flag 使用
******************************************************************/

#include <iostream>
#include <thread>
#include <mutex>  // std::call_once
using namespace std;

std::once_flag g_onceflag;

void call_once_test() {
    std::call_once(g_onceflag, []() {
        cout << "call_once_test" << endl;
    });
}

int main() {
    std::thread threads[5];
    for (int i = 0; i < 5; ++i) {
        threads[i] = std::thread(call_once_test);
    }

    for (auto& th : threads) {
        th.join();
    }

    return 0;
}

std::condition_varible

/****************************************************************
C++11 提供 std::condition_varible 线程同步方式

std::condition_varible 是一个能够阻止调用线程的对象,直到通知继续。
它是实现生产者-消费者问题(避免轮询,事件通知)和其他需要线程同步的情况时的重要工具
condition_variable类型的对象总是使用unique_lock等待

1.wait() :阻塞线程
2.notify_one() :通知一个线程的wait(),唤醒线程去尝试拿锁。拿锁失败返回false,成功则执行任务
3.notify_all() :通知所有线程
******************************************************************/

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;

std::deque<int> g_deque;
std::mutex g_mutex;
std::condition_variable g_cond;

void thread_producer() {
    int count = 10;
    while (count > 0) {
        std::unique_lock<std::mutex> lock(g_mutex);
        g_deque.push_front(count);
        lock.unlock();
        cout << "producer a value: " << count << endl;
        g_cond.notify_one();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        count--;
    }
}

void thread_consumer() {
    int data = 0;
    while (data != 1) {
        std::unique_lock<std::mutex> lock(g_mutex);
        while (g_deque.empty()) {
            g_cond.wait(lock);
        }
        data = g_deque.back();
        g_deque.pop_back();
        cout << "consumer a value: " << data << endl;
        lock.unlock();
    }
}

int main() {
    std::thread t1(thread_consumer);
    std::thread t2(thread_producer);

    t1.join();
    t2.join();

    return 0;
}

/* 运行结果:
 * producer a value: 10
 * consumer a value: 10
 * producer a value: 9
 * consumer a value: 9
 * producer a value: 8
 * consumer a value: 8
 * producer a value: 7
 * consumer a value: 7
 * producer a value: 6
 * consumer a value: 6
 * producer a value: 5
 * consumer a value: 5
 * producer a value: 4
 * consumer a value: 4
 * producer a value: 3
 * consumer a value: 3
 * producer a value: 2
 * consumer a value: 2
 * producer a value: 1
 * consumer a value: 1
 */

std::thread_local

c++11 引入 thread_local,用 thread_local 修饰的变量具有 thread 周期,每一个线程都拥有并只拥有一个该变量的独立实例,一般用于需要保证线程安全的函数中。

对于一个线程私有变量,一个线程拥有且只拥有一个该实例,类似于 static。

/*****************************
std::thread_local
******************************/

#include <iostream>
#include <thread>
using namespace std;

class A {
public:
    A() {}
    ~A() {}

    void test(const string& name) {
        thread_local int count = 0;
        ++count;
        cout << name << ": " << count << endl;
    }
};

void func(const string& name) {
    A a1;
    a1.test(name);
    a1.test(name);
    A a2;
    a2.test(name);
    a2.test(name);
}

int main() {
    std::thread(func, "thread1").join();
    std::thread(func, "thread1").join();

    return 0;
}
posted @ 2023-10-19 19:56  洋綮  阅读(19)  评论(0)    收藏  举报