C++: mutex, recursive_mutex, lock_guard, condition_variable và atomic

C++: mutex, recursive_mutex, lock_guard, condition_variable và atomic

Nguồn : http://baptiste-wicht.com/posts/2012/04/c11-concurrency-tutorial-advanced-locking-and-condition-variables.html

Từ trước tới giờ khi động tới multi threads C++ là mình biết dùng mỗistd:: mutex để quản lý tài nguyên và phối hợp giữa các thread. Thời gian gần đấy thấy có mấy code dùng tới std::condition_variable nên hôm nay ngứa ngáy phải tìm hiểu cho ra nó là cái gì.

std::mutex

Cái này từ trước đến giờ hay dùng nhiều nhất. std::mutex được dùng để bảo vệ tài nguyên, tránh việc nhiều thread đồng thời truy cập làm thay đổi tài nguyên khi mà có thread khác đang cần dùng tới.

struct Counter {
    int value;
    Counter() : value(0) {}

    void increment(){
        ++value;
    }
    void decrement(){
        if(value == 0){
            throw "Value cannot be less than 0";
        }
        --value;
    }
};

struct ConcurrentCounter {
    std::mutex mutex;
    Counter counter;

    void increment(){
        mutex.lock();
        counter.increment();
        mutex.unlock();
    }

    void decrement(){
        mutex.lock();
        counter.decrement();        
        mutex.unlock();
    }
};

std::recursive_mutex

C++ có 1 đặc điểm chết người đó là không được lock mutex 2 lần liên tiếp khi mà chưa unlock trong cùng 1 thread . Giai pháp khi cần phải lock 2 lần đó là dùng std::recursive_mutex

struct Complex {
    std::recursive_mutex mutex;
    int i;

    Complex() : i(0) {}

    void mul(int x){
        std::lock_guard<std::recursive_mutex> lock(mutex);
        i *= x;
    }

    void div(int x){
        std::lock_guard<std::recursive_mutex> lock(mutex);
        i /= x;
    }

    void both(int x, int y){
        std::lock_guard<std::recursive_mutex> lock(mutex);
        mul(x);
        div(y);
    }
};

Đoạn trên mà dùng std::mutex không thôi là đơ hết

std::lock_guard

std::lock_guard cho phép bạn dùng mutex trong một đoạn code, thay vì phải tự lockunlock ( đôi khi quên unlock là chương trình đơ luôn) chỉ cần gọi std::lock_guard.

Theo một số benmarch thì dùng lock_guard sẽ tốn ít CPU hơn dùng lock, unlock của mutex :
http://baptiste-wicht.com/posts/2012/07/c11-synchronization-benchmark.html

struct ConcurrentCounter {
    std::mutex mutex;
    Counter counter;

    void increment(){
       std::lock_guard<std::mutex> lk(mutex);
        counter.increment();
    }

    void decrement(){
        std::lock_guard<std::mutex> lk(mutex);
        counter.decrement();        
    }
};

std::condition_variable

Đây là phát hiện lớn nhất của mình trong bài.

Vấn đề trước đây, trong việc chia sẻ tài nguyên giữa các threads, giả sử chia sẻ 1 queue. Khi bạn muốn pop một thành phần trong queue để thực một việc thì sẽ thường phải check xem queue đó có khác rỗng hay không, ví dụ :

void worker1()
{
    if (queue.size() > 0)
    {
        std::lock_guard<std::mutex> lk(mutex);
        auto e = queue.front();
        queue.pop();
        do_some_thing(e);
    }
}

Đoạn trên có một vấn đề là nếu queue khác rỗng thì tốt, công việc sẽ được thực hiện. Nhưng làm sao mà biết được khi nào queue khác rỗng, cách cổ điển tôi hay dùng là làm thêm 1 vòng lặp while để check xem queue đã khác rỗng chưa


void worker1()
{
    while ( queue1.size() == 0 )
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Ngủ 10 ms để tránh CPU load 100% 
    }    
    std::lock_guard<std::mutex> lk(mutex);
    auto e = queue1.front();
    queue1.pop();
    do_some_thing(e);   
}

Cách trên dù đã cố dùng sleep để giảm thời gian CPU nhưng vẫn không được hay lắm, có một cách khác không cần phải dùng tới vòng while để kiểm tra một điều kiện, đó là dùng tới std::condition_variable.

Ví dụ bài toàn trên, thread add thêm thành phần vào queue có thể dùng std::condition_variable::notify_one để báo cho các thread đang chờ rằng queue khác rỗng. Trong nhiều trường hợp cần gửi thông báo tới nhiều threads thì dùng notify_all

std::mutex mutex;
std::condition_variable notEmpty;

void addQueueWorker()
{
    std::unique_lock<std::mutex> lk(mutex); 
    queue1.push(e)
    lk.unlock(); // dung unique_lock để gọi unlock trước khi kết thúc đoạn code thay vì lock_guard
    notEmpty.notify_one(); 
}

void worker1()
{

    std::lock_guard<std::mutex> lk(mutex);
    if ( queue.size() == 0)
   {
        // Chờ ở đây vì queue đang rỗng, việc chờ không tốn CPU time, khi nào có notify từ threads khác 
        // thì sẽ chạy đoạn code đằng sau
        notEmpty.wait(lk);
   }
    auto e = queue.front();
    queue.pop();
    do_some_thing(e);   
}

std::condition_variable là cách hay để gửi thông báo giữa các thread mà không tốn thời gian CPU

std::atomic

std::atomic được gọi là lock-free . Nghĩa là khi dùng atomic thì khỏi cần dùng lock để chia sẻ tài nguyên giữa các thread. Đáng tiếc là atomic cũng chỉ hỗ trợ một số type nhất định ( có thể check bằng atomic::is_lock_free() , và không đảm bảo hỗ trợ giống nhau ở các platform khác nhau, tốt nhất nên check. Tham khảo http://en.cppreference.com/w/cpp/atomic/atomic

Cách dùng, lại ví dù về Counter

#include <atomic>

struct AtomicCounter {
    std::atomic<int> value;

    void increment(){
        ++value;
    }

    void decrement(){
        --value;
    }

    int get(){
        return value.load();
    }
};
Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Manh Truong Tuan

6 bài viết.
2 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
3 3
Sử dụng Terminal hiệu quả hơn với tmux (Link) 1. Cài đặt bash $ sudo E addaptrepository ppa:pirho/dev $ sudo aptget update $ sudo aptget install...
Manh Truong Tuan viết hơn 1 năm trước
3 3
White
1 0
Bài toán là 1 mô hình kiểu mạng xã hội đơn giản, trong đó user nhấn vào 1 nút Add friend hoặc Follow để cho 1 user khác vào danh sách theo dõi ( để...
Manh Truong Tuan viết hơn 1 năm trước
1 0
White
1 0
Note về Regex Tổng hợp lại những kiến thức cơ bản về Regex mà mình đã được thực hành tại regexone.com Tìm kiếm đúng một chuỗi Cách này gần như ...
Manh Truong Tuan viết hơn 1 năm trước
1 0
Bài viết liên quan
White
19 2
Tiếp nối phần 1 http://kipalog.com/posts/7concurrencymodelsinsevenweekphan1. Trong phần này chúng ta sẽ tiếp tục tìm hiểu về mô hình ThreadLock th...
huydx viết hơn 1 năm trước
19 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
6 bài viết.
2 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!