POSIX執行緒程式設計起步(1)-Hello World (轉)

gugu99發表於2007-08-16
POSIX執行緒程式設計起步(1)-Hello World (轉)[@more@]


前言

在主機上,執行緒常常又被稱為“輕量級程式”,這種稱呼很簡單同時也便於理解,事實上,UNIX執行緒是從程式演變而來的。與程式相比,執行緒相當小,建立執行緒引起的開銷也相對較小。不僅如此,由於執行緒可以共享資源,而不像程式那樣擁有獨立的記憶體空間,所以使用執行緒也很節省記憶體。以後的幾篇文章,將重點講述POSIX 執行緒標準最常用的部分(主要基於其在DEC O/1 OS, V3.0上的實現)。

1.Hello World

建立程式所用的是pthread_create()。它的四個引數包括了:一個執行緒(pthread_t)變數的指標、一個執行緒屬性(pthread_attr_t)變數的指標、執行緒啟動時所要執行的函式指標以及傳遞給該函式的一個引數(void *)。


  pthread_t  a_thread;
  pthread_attr_t  a_thread_attribute;
  void *  thread_function(void *argument);
  char  *some_argument;
 
  pthread_create( &a_thread, a_thread_attribute, thread_function, (void *)some_argument);

很多時候,執行緒屬性變數僅僅指定執行緒使用的最小棧。其實它有更豐富的含義,但現在的情況是,大多數應用建立新執行緒時值不過傳遞了一個PTHREAD_ATTR_DEFAULT,有時甚至只是NULL。用pthread_create()建立出的新執行緒,從指定的函式入口開始,這與建立程式不同:所有的程式都具有相同的執行序列。這樣設計的原因很簡單:如果所有的程式都從同一程式空間的同一處開始執行,那麼就會有多個程式對相同的共享資源執行相同的指令。

現在我們已經知道如何建立新執行緒,就讓我們來開始第一個多執行緒程式:用多個執行緒在螢幕上輸出“Hello World”。為了顯示執行緒的作用,我們將用到兩個執行緒:一個用來輸出“Hello”、另一個用來輸出“World”。為此,我們首先需要一個用於螢幕輸出的函式,新執行緒將從此函式開始執行。此外,我們還需要兩個執行緒(pthread_t)變數,用來建立新執行緒。當然,我們需要在pthread_create()的引數中指明每個新執行緒應該輸出的字串。請看以下程式碼:

  void * print_message_function( void *ptr );
 
  main()
  {
  pthread_t thread1, thread2;
  char *message1 = "Hello";
  char *message2 = "World";
 
  pthread_create( &thread1, pthread_attr_default,
  print_message_function, (void*) message1);
  pthread_create(&thread2, pthread_attr_default,
  print_message_function, (void*) message2);
 
  exit(0);
  }
 
  void * print_message_function( void *ptr )
  {
  char *message;
  message = (char *) ptr;
  printf("%s ", message);
  }

這裡需要注意的是print_message_function()函式的原型,以及建立新執行緒時對引數型別的轉換。程式首先建立第一個新執行緒並將“Hello”作為引數傳遞,接著建立了另一個執行緒並傳遞“World”作為起始引數。我們希望第一個執行緒從printf_message_function()開始執行,在輸出“Hello”後結束,接著第二個執行緒在輸出“World”之後也同樣地結束。這樣的過程看起來似乎很合理,然而其中有兩處嚴重缺陷。

首先,不同的執行緒是並行執行的,並無先後次序。因此我們無法保證第一個新執行緒在第二執行緒之前輸出字串。其結果是,螢幕輸出可能是“Hello World”,也可能是“World Hello”。其次,與上述原因類似,父執行緒(姑且如此稱呼)有可能在兩個子執行緒輸出之前就執行了exit(0),這將導致整個程式結束——當然兩個子程式也就因此而結束了。其後果是螢幕上可能根本沒有輸出。為了解決第二個問題,我們可以用pthread_exit()來代替exit(),這樣兩個子程式就不會結束(因為該函式不會終止整個程式的執行)。

目前我們的小程式有兩個競爭條件,現在讓我們試著用比較笨的辦法來解決它們。首先,為了讓兩個子執行緒按照我們需要的順序執行,我們在建立第二個執行緒之前插入一個延遲。接著,為了保證在子執行緒結束之前父執行緒不退出,我們在父執行緒的尾部也插入一個延遲。請看下面的程式碼:

  void * print_message_function( void *ptr );
 
  main()
  {
  pthread_t thread1, thread2;
  char *message1 = "Hello";
  char *message2 = "World";
 
  pthread_create( &thread1, pthread_attr_default,
  print_message_function, (void *) message1);
  sleep(10);
  pthread_create(&thread2, pthread_attr_default, 
  print_message_function, (void *) message2);
 
  sleep(10);
  exit(0);
  }
 
  void * print_message_function( void *ptr )
  {
  char *message;
  message = (char *) ptr;
  printf("%s", message);
  pthread_exit(0);
  }

令人遺憾的是,以上程式碼仍然不能達到我們的目的。利用延遲來進行執行緒同步是很不可靠的。我們目前遇到的同步問題本質上與分散式中的同步問題相同:我們永遠無法確知某一個執行緒將會在何時結束。
 
以上程式碼的缺陷不只是不可靠,事實上sleep()函式執行時,整個程式都在睡覺而不僅僅是父執行緒,這一點和exit()很像。當sleep()返回時,我們的程式仍然面對著相同的條件競爭。我們的新程式碼不僅沒有解決競爭問題,反而讓我們多花了20秒來等待程式結束。順便應該指出,如果想要對某一執行緒進行延遲,應該pthread_delay_np()函式(np意指non portable,不可移植),如下:

  struct timespec delay;
  delay.tv_sec = 2;
  delay.tv_nsec = 0;
  pthread_delay_np( &delay );

本節涉及的函式:
 pthread_create(), pthread_exit(), pthread_delay_np().


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

相關文章