多執行緒統計多個檔案的單詞數目---C++0x多執行緒使用示例

iDotNetSpace發表於2009-12-02

對多執行緒經驗不多,僅提供一些個人看法,如有錯誤請指正。

1.C++0X 多執行緒簡介
C++0x STL提供了對多執行緒的支援就不用再去選擇跨平臺的多執行緒庫了,用標準的吧:)

看了一下BOOST和當前STL的介面幾乎完全一致:)也就是說用boost thread寫的程式應該把例如boost::thread, boost::unique_lock ...等等的地方換成std::thread, std::unique_lock...就OK了,個人覺得,不過我還沒用過boost thread.所以說熟悉pthread的應該能很快上手,而熟悉boost thread應就可以直接上手了~

但是現在GCC還不支援thread local變數。不支援原子操作。基本的mutex,condional 變數等等都支援了。

關於C++0X thread的入門介紹請參考:

Simpler Multithreading in C++0x
http://www.devx.com/SpecialReports/Article/38883/0/page/1

       該文有中文的翻譯

C++0x 概覽: 多執行緒(1) - VC++ - 逆風者

www.upwinder.com/www/c1/2999.html

Multithreading in C++0x
http://www.justsoftwaresolutions.co.uk/threading/multithreading-in-c++0x-part-1-starting-threads.html

[譯]放棄原來的執行緒API,採用新的C++執行緒
www.cppprog.com/2009/0102/30.html
另外使用C++0X的話大家最好了解一下所謂的右值引用和move語義,個人覺得這兩個概念很有用處的,算是突破性的概念了,當然不清楚也可以用thread.參考劉未鵬寫的,《C++0x漫談》系列之:右值引用(或“move語意與完美轉發”)(上),既可
http://blog.csdn.net/pongba/archive/2007/07/10/1684519.aspx

或者看A Brief Introduction to Rvalue References http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html

 
2. 如何編譯執行 
我只在linux下用GCC4.4.2試過。能夠除錯成功。

首先你要確保你的GCC編譯時是thread enable的一般預設安裝就是enable的了。可以用gcc -v檢視一下:

allen:~/study/unix_system/CH14$ gcc -v
使用內建 specs。
目標:i686-pc-linux-gnu
配置為:../configure
執行緒模型:posix
gcc 版本 4.4.2 (GCC)

如上顯示執行緒模型posix,表示就是可以用多執行緒了否則顯示為空。注意本質上還是posix多執行緒所以你要加編譯引數-pthread

同時標明使用 c++0x特性, -std=c++0x 或者-std=gnu0x

例如:

g++ -g -o wordcount wordcount.cc -std=c++0x -pthread

 
3.一個完整的小程式示例,多執行緒文字單詞數目統計
首先說一下程式目的,多個執行緒每個統計一個文字的單詞數目,作為實驗程式,這裡演示3個執行緒統計的例子。這個例子會涉及到執行緒的同步,因為要每個執行緒統計完之後向主執行緒報告自己已經完成統計並且將資訊寫到互斥區的mail_box中。而主執行緒會將資訊從mail_box中讀出,然後通知下一個執行緒可以寫mail_box.就是說當3個統計單詞的執行緒看為寫者,主執行緒看為讀者,一個時間只能有一個執行緒訪問mail_box讀或者寫。
這個問題來源是《unix 程式設計實踐教程》p14.5.2使用條件變數編寫程式 一節的例子 twocount4.c的例子改寫的,原文中使用pthread, 但是它是有問題的,因為書中的例子是2個執行緒負責統計,如果擴充到3個會死鎖,主要由於書中的例子只用了一個條件變數,並沒有區分讀者通知和寫者通知。我以前寫過一篇文章分析這個情況多執行緒同步問題 條件變數和訊號量  http://www.cnblogs.com/rocketfan/archive/2009/07/24/1530477.html       
當時我對條件變數理解的還不是很清楚,所以用訊號量寫了一個正確的版本。其實mutex+條件變數可以解決一切用訊號量能解決的問題。不過我還是不是很清楚為什麼c++0x STL不提供訊號量,boost thread也不提供而是存在在boost interprocess庫中,好像大家用多執行緒也基本不用訊號量,為什麼呢,誰比較清楚給我詳細解釋下,謝謝。確實條件變數更強大,但我始終覺得用訊號量還是更清晰一些的,作業系統課本也基本都用訊號量舉例。比如作業系統核心設與設計原理一書。例如這個問題用訊號量可以表示如下,當然用條件變數類似:

write = 0
 
read = 0
 
mail = null
 
//server  main thread
 
v(read) //to let one client sub thread can write at first since mail == null   
 
for (i =0 ; i < sub threads num; i++)
 
  p(write)  
 
  read mail
 
  mail = null
 
  v(read)    
 
//clinent  sub threads
 
p(read)
 
write mail
 
v(write)
 

用C++0X,mutex + conditional variable 的實現,如下, 有很詳細的註釋的,如有問題還請指正:
 

 

程式碼
/**
 *  ==============================================================================
 *
 *          \file   wordcount.cc
 *
 *        \author   chenghuige@gmail.com
 *
 *          \date   2009-12-02 09:55:25.197674
 * 
 *   Description:   演示c++0x,執行緒建立結束,mutex,條件變數的使用,注意為了簡單名字未加
 *                  std::
 *  ==============================================================================
 */


#include
#include
#include
#include
using namespace std;

mutex m;
condition_variable reader_cond;   //cond notify reader
condition_variable writer_cond;   //cond2 notify writer

struct MailBox {
  string file_name;  //which file
  int count;         //how many words
  int tid;           //which thread write
};


MailBox mail_box;

class WordCounter
{
private:
  int tid_;
  string infile_name_;
public:
  WordCounter(int tid, string infile_name):
    tid_(tid),infile_name_(infile_name){}

  void operator()() {
    int c, prevc = '\0';
    int count = 0;
   
    //統計文字的單詞數目
    ifstream input_file(infile_name_.c_str(),ios::binary);
    input_file.unsetf(ios::skipws); // 要接受空格符
    istreambuf_iterator eos;               // end-of-range iterator
    istreambuf_iterator iit (input_file);
    for (; iit!=eos; ++iit) {
      c = *iit;
      if ( !isalnum(c) && isalnum(prevc) )
        count++;
      prevc = c;
    }
    input_file.close();
   
    cout << "COUNT " << tid_ << " waiting to get lock" << endl;
   
    unique_lock lk(m);

    cout << "COUNT " << tid_ << " have lock, store data" << endl;

    //如果mail_box不是空的,注意mail_box是互斥區所保護的
    while (!mail_box.file_name.empty()) {
      cout << "COUNT " << tid_ << " oops.. mail box not empty, wait for signal" << endl;
      //等待讀者讀完並通知,寫著釋放鎖控制
      writer_cond.wait(lk);  //注意wait只接受unique_lock不接受lock_guard
      //重新鎖住互斥區
    }
    cout << "COUNT " << tid_ << " OK, I can write mail" << endl;
         
    mail_box.file_name = infile_name_;
    mail_box.count = count;
    mail_box.tid = tid_;
  
    cout << "COUNT " << tid_ << " rasing flag" << endl;
    //通知讀者我已經寫完你可以讀了
    reader_cond.notify_one();
    //注意在這裡我還是擁有鎖的,所以儘管讀者已經借到可讀通知了,但是它還是會被卡到互斥區外的
    cout << "COUNT " << tid_ << " Finished writting.Words are " << count << " for file "
         << infile_name_ << endl;


    cout << "COUNT " << tid_ << " will unlock" << endl;
    //在結束的時候lk的解構函式會自動呼叫解鎖,注意這之後不一定是讀者能夠獲得互斥區,有可能
    //被其它寫者搶先搶到但是沒關係,它們發現mail_box非空,會wait,並且釋放互斥區的,所以可能
    //出現下面的情況
    //COUNT 0 will unlock
    //COUNT 1 have lock, store data
    //COUNT 1 oops.. mail box not empty, wait for signal
    //COUNT 2 have lock, store data
    //COUNT 2 oops.. mail box not empty, wait for signal
    //MAIN: Wow! flag was raised, I have the lock
    //11 1.log 0
    //Main has finished reading
  }

};

void read_mail(char *argv[])
{
  
  //讀者要先鎖住互斥區
  unique_lock lk(m);
  cout << "Main locking the box" << endl;
 
  //建立3個執行緒,注意這裡採用的是建立空執行緒,
  //然後利用臨時變數的move,執行緒是不可拷貝但是可move的
  thread t[3];
  for (int i = 0; i < 3; i++)
    t[i] = thread(WordCounter(i, argv[i + 1]));

  int total_words = 0;

  //讀者要等待3個寫者統計完文字資訊
  for(int reports_in = 0; reports_in < 3; reports_in++) {
    cout << "MAIN: waiting for flag to go up" << endl;
  
    //等待一個寫者寫完mail,同時釋放指定的鎖m
    reader_cond.wait(lk);
    //收到寫者寫完的訊號,到達這裡,讀者可以讀了,同時鎖住互斥區
    cout << "MAIN: Wow! flag was raised, I have the lock" << endl;
   
    cout << mail_box.count << " " << mail_box.file_name << " " << mail_box.tid << endl;
    total_words += mail_box.count;
   
    //讀者讀完後將mail_box的檔名置空,標明已經讀完
    mail_box.file_name.clear();

    cout << "Main has finished reading" << endl;
   
    //通知寫者我已經讀完mail box
    writer_cond.notify_one(); 
  }

  for (int i =0; i < 3; i++) {
    t[i].join();
  }

  cout << total_words << ": total words" << endl;
}

int main(int argc, char *argv[])
{
   
  if ( argc != 4 ){
    cout << "usage: " << argv[0] << " file1 file2 file3" << endl;
    exit(1);
  }

  read_mail(argv);
}


 

執行結果
Main locking the box
COUNT 0 waiting to get lock
COUNT 1 waiting to get lock
COUNT 2 waiting to get lock
MAIN: waiting for flag to go up
COUNT 0 have lock, store data
COUNT 0 OK, I can write mail
COUNT 0 rasing flag
COUNT 0 Finished writting.Words are 11 for file 1.log
COUNT 0 will unlock
COUNT 1 have lock, store data
COUNT 1 oops.. mail box not empty, wait for signal
COUNT 2 have lock, store data
COUNT 2 oops.. mail box not empty, wait for signal
MAIN: Wow! flag was raised, I have the lock
11 1.log 0
Main has finished reading
MAIN: waiting for flag to go up
COUNT 1 OK, I can write mail
COUNT 1 rasing flag
COUNT 1 Finished writting.Words are 128 for file twordcount1.c
COUNT 1 will unlock
MAIN: Wow! flag was raised, I have the lock
128 twordcount1.c 1
Main has finished reading
MAIN: waiting for flag to go up
COUNT 2 OK, I can write mail
COUNT 2 rasing flag
COUNT 2 Finished writting.Words are 382 for file twordcount4_semaphore.c
COUNT 2 will unlock
MAIN: Wow! flag was raised, I have the lock
382 twordcount4_semaphore.c 2
Main has finished reading
521: total words


 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-621164/,如需轉載,請註明出處,否則將追究法律責任。

相關文章