2013-08-16 108 views
8

Tôi viết một chương trình đơn giản để kiểm tra Chủ đề trong C++ 11 nhưng std::cout không hoạt động như tôi mong đợi.sử dụng std :: cout trong nhiều chủ đề

class Printer 
{ 
public: 
    void exec() 
    { 
     mutex m; 
     m.lock(); 
     cout<<"Hello "<<this_thread::get_id()<<endl; 
     chrono::milliseconds duration(100); 
     this_thread::sleep_for(duration); 
     m.unlock(); 

    } 
}; 

int main() 
{ 
    Printer printer; 

    thread firstThread([&printer](){ 
     while(1) 
      printer.exec(); 

    }); 
    thread secondThread([&printer](){ 
     while(1) 
      printer.exec(); 
    }); 

    firstThread.join(); 
    secondThread.join();  
} 

một số kết quả:

Hello 11376 
Hello 16076 
Hello 16076 
Hello Hello 11376 
16076 
Hello 11376 
,.... 

tôi đã sử dụng mutex cho khóa đề vì vậy tôi không thể hiểu tại sao hai luồng đang thực hiện std::cout cùng một lúc. Đường nối rất mệt mỏi với tôi. Có ai giải thích điều gì đang xảy ra không?!

+1

Khi bạn triển khai giải pháp được cung cấp trong câu trả lời, bạn cũng nên chuyển sang sử dụng 'lock_guard' thay vì các cuộc gọi khóa/mở khóa thủ công. Và đừng làm phiền (ví dụ, với 'endl'); chỉ cần sử dụng '\ n'. – bames53

Trả lời

21

Các chủ đề đang sử dụng khác nhaumutex trường hợp như mutex là một biến địa phương trong exec() chức năng để khóa mutex là vô nghĩa vì mỗi chủ đề sẽ được khóa riêng mutex của nó dẫn đến không đồng bộ giữa các chủ đề. Cùng một ví dụ mutex phải được sử dụng bởi các chủ đề để đạt được đồng bộ hóa.

Để sửa trong mã được đăng, hãy tạo mutex biến thành viên. Tuy nhiên, nếu một đối tượng Printer khác được tạo thì sẽ không có sự đồng bộ hóa giữa các chủ đề sử dụng các phiên bản Printer khác nhau. Trong trường hợp này, mutex sẽ cần phải là một biến static thành viên để đảm bảo đồng bộ hóa:

class Printer 
{ 
public: 
    //... 
private: 
    static std::mutex mtx_; 
}; 

std::mutex Printer::mtx_; 

Để đảm bảo một mutex luôn phát hành, bất kể nếu một chức năng thoát bình thường hoặc thông qua một ngoại lệ, sử dụng std:lock_guard:

std::lock_guard<std::mutex> lock(m); // 'm' locked, and will be 
            // unlocked when 'lock' is destroyed. 
std::cout<< "Hello " << std::this_thread::get_id() << std::endl; 
std::chrono::milliseconds duration(100); 
std::this_thread::sleep_for(duration); 
+0

Khắc phục là khai báo 'mutex' với thời gian lưu trữ' tĩnh'. ... hay thế. – Casey

+0

@Casey hoặc biến thành viên khi chuỗi đang sử dụng cùng một phiên bản 'Máy in'. – hmjd

+1

@hjmd Tôi muốn 'static' trong trường hợp này tương ứng với phạm vi của' std :: cout'. Bằng cách đó, nó sẽ tiếp tục hoạt động khi một số lập trình viên bảo trì xuống dòng tạo ra một 'Máy in' khác trong một số chức năng khác. – Casey

1

bạn có thể xem xét một toàn cầu std::mutex cout_mutex; (nơi nào đó trong không gian tên của bạn), được sử dụng để bảo vệ std::cout đầu ra. Hãy chắc chắn rằng bạn sử dụng std::lock<std::mutex> (vì vậy bạn không thể quên mở khóa các mutex và an toàn ngoại lệ).

11

Câu trả lời được chấp nhận là chính xác. Tuy nhiên, tốt nhất là tách riêng các mối quan ngại:

  1. Bạn cần một cách để in thành std::cout theo chủ đề an toàn.
  2. Bạn cần tạo đối tượng/hàm/hàm để chạy trong chuỗi và khởi chạy chúng.

Dưới đây là một tiện ích tôi sử dụng mà chỉ tập trung vào việc thu thập luận cứ để std::cout và streaming chúng ra dưới một static std::mutex:

#include <iostream> 
#include <mutex> 

std::ostream& 
print_one(std::ostream& os) 
{ 
    return os; 
} 

template <class A0, class ...Args> 
std::ostream& 
print_one(std::ostream& os, const A0& a0, const Args& ...args) 
{ 
    os << a0; 
    return print_one(os, args...); 
} 

template <class ...Args> 
std::ostream& 
print(std::ostream& os, const Args& ...args) 
{ 
    return print_one(os, args...); 
} 

std::mutex& 
get_cout_mutex() 
{ 
    static std::mutex m; 
    return m; 
} 

template <class ...Args> 
std::ostream& 
print(const Args& ...args) 
{ 
    std::lock_guard<std::mutex> _(get_cout_mutex()); 
    return print(std::cout, args...); 
} 

Mã này có thể được tái sử dụng cho các dòng khác hơn std::cout, nhưng ở trên là chuyên chỉ nhắm mục tiêu std::cout. Với điều này Printer::exec() của bạn bây giờ có thể được đơn giản hóa đáng kể:

void exec() 
{ 
    print("Hello ", std::this_thread::get_id(), '\n'); 
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
} 

Bây giờ, không chỉ sẽ bạn Printer sử dụng cout một cách ThreadSafe, và đã được đơn giản hóa (ví dụkhông cần duy trì riêng mutex cho cout) của mình, nhưng tất cả các loại và chức năng khác của bạn cũng có thể sử dụng cout và tất cả đều tương tác với nhau một cách an toàn. Chức năng print giờ đây duy trì mutex và thực tế đó được gói gọn khỏi tất cả các khách hàng của print.

+5

in là một chức năng mẫu. Tôi nghĩ rằng bạn có thể kết thúc với nhiều hơn một mutex. –

+1

@JanChristophUhde: Quan sát tuyệt vời! Và chỉ mất 3 năm để ai đó chú ý! :-) Đã sửa. Cảm ơn. –

3

Tôi đang chia sẻ mẹo từ Nicolá được đưa ra trong this question mà tôi thấy thanh lịch hơn so với việc thực hiện Howard Hinnant. Ý tưởng là tạo ra một đối tượng ostringstream tạm thời và đặt mã bảo vệ trên destructor.

/** Thread safe cout class 
    * Exemple of use: 
    * PrintThread{} << "Hello world!" << std::endl; 
    */ 
class PrintThread: public std::ostringstream 
{ 
public: 
    PrintThread() = default; 

    ~PrintThread() 
    { 
     std::lock_guard<std::mutex> guard(_mutexPrint); 
     std::cout << this->str(); 
    } 

private: 
    static std::mutex _mutexPrint; 
}; 

std::mutex PrintThread::_mutexPrint{}; 

Sau đó bạn có thể sử dụng nó như là một thường xuyên std::cout, từ bất kỳ chủ đề:

PrintThread{} << "val = " << 33 << std::endl; 

Đối tượng thu thập dữ liệu như một thường xuyên std::ostringstream. Ngay sau khi đạt được hôn mê, đối tượng bị phá hủy và tuôn ra tất cả các thông tin thu thập được.