執行緒安全佇列(使用互斥鎖進行實現)

227569hy發表於2024-05-11

執行緒安全佇列(使用互斥鎖進行實現)

沒有設定佇列上限的執行緒安全佇列

只需要採取一個std::condition_variable變數,用於處理佇列為空的情況

以下是示例程式碼,涉及了std::mutex和std::condition_variable、std::unique_lock、std::lockguard等多執行緒互動的類。
測試方式採取的是3個生成者和一個消費者,消費者監測生產者push的資料是否有異常,同時拿一個執行緒在十秒後呼叫Finish()函式。

示例1:沒有設定佇列上限的執行緒安全佇列(包含測試程式碼)

#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <atomic>
#include <functional>
#include <string>
#include <chrono>
#include <map>

template <typename T>
class ThreadSafetyQueue
{
public:
    ThreadSafetyQueue() : finish_(false) {}


    bool push(T &data)
    {
        std::unique_lock<std::mutex> lock(mtx_);
        if(finish_)
        {
            std::cout << "queue finish" << std::endl;
            return false;
        }
        data_queue_.push(data);
        lock.unlock(); // 注意:解鎖必須在通知之前
        cv_.notify_one();
        return true;
    }

    bool pop(T &data)
    {
        std::unique_lock<std::mutex> lock(mtx_);
        while(!finish_ && data_queue_.empty()) // 防止假醒
        {
            cv_.wait(lock);
        }
        if(finish_)
        {
            std::cout << "queue finish" << std::endl;
            return false;
        }
        data = data_queue_.front();
        data_queue_.pop();
        return true;
    }

    bool empty()
    {
        std::unique_lock<std::mutex> lock(mtx_);
        if(data_queue_.empty())
        {
            return true;
        }
        return false;
    }

    int size()
    {
        std::unique_lock<std::mutex> lock(mtx_);
        return data_queue_.size();
    } 

    void Finish()
    {
        finish_ = true;
        cv_.notify_all();
    }

private:
    std::mutex mtx_;
    std::queue<T> data_queue_;
    std::condition_variable cv_;
    std::atomic_bool finish_;
};

struct test_data
{
    int num;
    std::string str;
};

int main()
{
    ThreadSafetyQueue<test_data> queue;
    std::function<void(std::string)> product_func = ([&queue](std::string name){
        for(int i = 0; i < 1000000; ++i)
        {
            test_data td{i, name};
            queue.push(td);
        }
    });

    std::function<void(void)> consumer_func = ([&queue](){
        std::map<std::string, int> test_mp;
        while(1)
        {
            test_data data;
            if(queue.pop(data))
            {
                if(test_mp.find(data.str) == test_mp.end())
                {
                    test_mp[data.str] = 0;
                    std::cout << "add consumer name is: " << data.str << std::endl;
                }

                if(test_mp[data.str] == data.num)
                {
                    test_mp[data.str] ++;
                }
                else
                {
                    std::cout << "error" << std::endl;
                    std::cout << "consumer name is: " << data.str << std::endl;
                    std::cout << "consumer " << data.num << std::endl;
                }
            }
        }
    });

    std::function<void(void)> finish_func = ([&queue](){
        std::this_thread::sleep_for(std::chrono::seconds(10));
        queue.Finish();
    });

    std::thread producer1(product_func, "producer1");
    std::thread producer2(product_func, "producer2");
    std::thread producer3(product_func, "producer3");
    std::thread consumer1(consumer_func);
    std::thread finish_thread(finish_func);

    while (1)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    queue.Finish();
    producer1.join();
    producer2.join();
    producer3.join();
    consumer1.join();
    finish_thread.join();
    
    return 0;
}

設定佇列上限的執行緒安全佇列

需要兩個std::condition_variable變數,用於處理佇列為空和佇列為滿的情況。
增加了設定MaxSize的函式。

示例中,size=0表示佇列沒有上限。

示例2:設定佇列上限的執行緒安全佇列(包含測試程式碼)
#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <atomic>
#include <functional>
#include <string>
#include <chrono>
#include <map>

template <typename T>
class ThreadSafetyQueue
{
public:
    ThreadSafetyQueue(size_t size = 0) : 
    finish_{false},
    max_size_{size}
    {
    }


    bool push(T &data)
    {
        std::unique_lock<std::mutex> lock(mtx_);
        while (CheckFull() && !finish_)
        {
            // std::cout << "queue full" << std::endl;
            full_cv_.wait(lock);
        }
        
        if(finish_)
        {
            // std::cout << "queue finish" << std::endl;
            return false;
        }
        data_queue_.push(data);
        lock.unlock(); // 解鎖在notify_one之前更合適
        empty_cv_.notify_one();
        return true;
    }

    bool pop(T &data)
    {
        std::unique_lock<std::mutex> lock(mtx_);
        while(!finish_ && data_queue_.empty()) // 防止假醒
        {
            empty_cv_.wait(lock);
        }
        if(finish_)
        {
            // std::cout << "queue finish" << std::endl;
            return false;
        }
        data = data_queue_.front();
        data_queue_.pop();
        full_cv_.notify_one();
        return true;
    }

    bool empty()
    {
        std::unique_lock<std::mutex> lock(mtx_);
        if(data_queue_.empty())
        {
            return true;
        }
        return false;
    }

    bool full()
    {
        std::unique_lock<std::mutex> lock(mtx_);
        if(data_queue_.size() >= max_size_)
        {
            return true;
        }
        return false;
    }

    int size()
    {
        std::unique_lock<std::mutex> lock(mtx_);
        return data_queue_.size();
    } 

    void Finish()
    {
        finish_ = true;
        full_cv_.notify_all();
        empty_cv_.notify_all();
    }

    void SetMaxSize(size_t size)
    {
        std::unique_lock<std::mutex> lock(mtx_);
        max_size_ = size;
        lock.unlock();
        full_cv_.notify_all();
    }

private:
    std::mutex mtx_;
    std::queue<T> data_queue_;
    std::condition_variable full_cv_;
    std::condition_variable empty_cv_;
    volatile size_t max_size_;
    std::atomic_bool finish_;

    bool CheckFull()
    {
        if(max_size_ == 0)
            return false;
        else 
            return data_queue_.size() >= max_size_;
    }
};

struct test_data
{
    int num;
    std::string str;
};

int main()
{
    ThreadSafetyQueue<test_data> queue(1000);
    std::function<void(std::string)> product_func = ([&queue](std::string name){
        for(int i = 0; i < 1000000; ++i)
        {
            test_data td{i, name};
            queue.push(td);
        }
    });

    std::function<void(void)> consumer_func = ([&queue](){
        std::map<std::string, int> test_mp;
        while(1)
        {
            test_data data;
            if(queue.pop(data))
            {
                if(test_mp.find(data.str) == test_mp.end())
                {
                    test_mp[data.str] = 0;
                    std::cout << "add consumer name is: " << data.str << std::endl;
                }

                if(test_mp[data.str] == data.num)
                {
                    test_mp[data.str] ++;
                }
                else
                {
                    std::cout << "error" << std::endl;
                    std::cout << "consumer name is: " << data.str << std::endl;
                    std::cout << "consumer " << data.num << std::endl;
                }
            }
        }
    });

    std::function<void(void)> finish_func = ([&queue](){
        std::this_thread::sleep_for(std::chrono::seconds(10));
        queue.Finish();
    });

    std::thread producer1(product_func, "producer1");
    std::thread producer2(product_func, "producer2");
    std::thread producer3(product_func, "producer3");
    std::thread consumer1(consumer_func);
    std::thread finish_thread(finish_func);

    while (1)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    queue.Finish();
    producer1.join();
    producer2.join();
    producer3.join();
    consumer1.join();
    finish_thread.join();
    
    return 0;
}



相關文章