Pthreads並行編程入門

常見的並行編程模型

  • Manager/Worker: manager線程負責分配任務給其餘worker線程。manager處理輸入輸出。又分爲static worker pool和dynamic worker pool
  • Pipeline:跟流水線同樣,每一個線程負責不同的任務,可是這些任務又是有順序的
  • Peer:與manager/worker類似,只是manager建立了其餘線程以後也會加入工做

線程安全的定義

refer to an application's ability to execute multiple threads at the same time without "clobbering" shared
​ data or creating "race" conditions.編程

Thread limits

Pthreads API是ANSI/IEEE標準,可是標準未指明的地方,各類不一樣的實現可能不一樣。安全

Pthread API

API分爲4部分app

類型 功能
thread management create, detach, join thread; get/set thread attributes
mutex Routines that deal with synchronization. an abbreviation for 'mutual exclusion', create, destroy, lock, unlock mutexes
condition variable address communication between threads that share a mutex.
synchronization Routines that manage read/write locks and barriers.

每一個使用Pthread庫的source file都應該include .h函數

Thread Management

建立線程與退出線程

pthread_create (thread,attr,start_routine,arg);//thread:線程對象的地址;attr:線程屬性對象;start_routine:線程啓動後執行的例程;arg:傳遞給例程的參數
pthread_exit (status);//返回狀態碼,該方法並不關閉文件,任何線程執行過程當中打開的文件在線程關閉以後仍會打開
pthread_cancel (thread);
pthread_attr_init (attr);
pthread_attr_destroy (attr);

pthread_attr_init函數用來初始化一個pthread_attr_t對象, pthread_attr_destroy摧毀一個pthread_attr_t對象ui

There are several ways in which a thread may be terminated:this

  1. The thread returns normally from its starting routine. Its work is done.
  2. The thread makes a call to the pthread_exit subroutine - whether its work is done or not.
  3. The thread is canceled by another thread via the pthread_cancel routine.
  4. The entire <u>process</u> is terminated due to making a call to either the exec() or exit()
  5. If main() finishes first, without calling pthread_exit explicitly itself

There is a definite problem if main() finishes before the threads it spawned if you don't call pthread_exit() explicitly. All of the threads it created will terminate because main() is done and no longer exists to support the threads. spa

By having main() explicitly call pthread_exit() as the last thing it does, main() will block and be kept alive to support the threads it created until they are done.
線程

example 1code

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS    5

void *PrintHello(void *threadid)
{
   long tid;
   tid = (long)threadid;
   printf("Hello World! It's me, thread #%ld!\n", tid);
   pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
   pthread_t threads[NUM_THREADS];
   int rc;
   long t;
   for(t=0;t<NUM_THREADS;t++){
     printf("In main: creating thread %ld\n", t);
     rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
     if (rc){
       printf("ERROR; return code from pthread_create() is %d\n", rc);
       exit(-1);
       }
     }

   /* Last thing that main() should do */
   pthread_exit(NULL);//若是不調用這個,由於上面沒有采用任何同步方式,main可能在其餘子線程前結束,會有問題,因此pthread_exit()會讓main阻塞,直到子線程執行完畢
}
Questions
  1. What's the difference between exit and pthread_exit?

    stackoverflow上說,exit performs normal program termination for the entire process while pthread_exit terminate a thread whether its work is done or not. pthread_exit kills calling thread.orm

example 2

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS    8

char *messages[NUM_THREADS];

void *PrintHello(void *threadid)
{
   long taskid;

   sleep(1);
   taskid = (long) threadid;
   printf("Thread %d: %s\n", taskid, messages[taskid]);
   pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
long taskids[NUM_THREADS];
int rc, t;

messages[0] = "English: Hello World!";
messages[1] = "French: Bonjour, le monde!";
messages[2] = "Spanish: Hola al mundo";
messages[3] = "Klingon: Nuq neH!";
messages[4] = "German: Guten Tag, Welt!"; 
messages[5] = "Russian: Zdravstvuyte, mir!";
messages[6] = "Japan: Sekai e konnichiwa!";
messages[7] = "Latin: Orbis, te saluto!";

for(t=0;t<NUM_THREADS;t++) {
  taskids[t] = t;
  printf("Creating thread %d\n", t);
  rc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskids[t]);
  if (rc) {
    printf("ERROR; return code from pthread_create() is %d\n", rc);
    exit(-1);
    }
  }

pthread_exit(NULL);
}

由於PrintHello中調用了sleep(1)因此,運行時大部分狀況下,create thread語句會先輸出。

example 3

如何傳遞多個參數:

/******************************************************************************
* FILE: hello_arg2.c
* DESCRIPTION:
*   A "hello world" Pthreads program which demonstrates another safe way
*   to pass arguments to threads during thread creation.  In this case,
*   a structure is used to pass multiple arguments.
* AUTHOR: Blaise Barney
* LAST REVISED: 01/29/09
******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS    8

char *messages[NUM_THREADS];

struct thread_data
{
   int    thread_id;
   int  sum;
   char *message;
};

struct thread_data thread_data_array[NUM_THREADS];

void *PrintHello(void *threadarg)
{
   int taskid, sum;
   char *hello_msg;
   struct thread_data *my_data;

   sleep(1);
   my_data = (struct thread_data *) threadarg;
   taskid = my_data->thread_id;
   sum = my_data->sum;
   hello_msg = my_data->message;
   printf("Thread %d: %s  Sum=%d\n", taskid, hello_msg, sum);
   pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int *taskids[NUM_THREADS];
int rc, t, sum;

sum=0;
messages[0] = "English: Hello World!";
messages[1] = "French: Bonjour, le monde!";
messages[2] = "Spanish: Hola al mundo";
messages[3] = "Klingon: Nuq neH!";
messages[4] = "German: Guten Tag, Welt!"; 
messages[5] = "Russian: Zdravstvytye, mir!";
messages[6] = "Japan: Sekai e konnichiwa!";
messages[7] = "Latin: Orbis, te saluto!";

for(t=0;t<NUM_THREADS;t++) {
  sum = sum + t;
  thread_data_array[t].thread_id = t;
  thread_data_array[t].sum = sum;
  thread_data_array[t].message = messages[t];
  printf("Creating thread %d\n", t);
  rc = pthread_create(&threads[t], NULL, PrintHello, (void *) 
       &thread_data_array[t]);
  if (rc) {
    printf("ERROR; return code from pthread_create() is %d\n", rc);
    exit(-1);
    }
  }
pthread_exit(NULL);
}

example 4:

錯誤傳參方法:main中會改變t!

/*****************************************************************************
* FILE: hello_arg3.c
* DESCRIPTION:
*   This "hello world" Pthreads program demonstrates an unsafe (incorrect)
*   way to pass thread arguments at thread creation.  In this case, the
*   argument variable is changed by the main thread as it creates new threads.
* AUTHOR: Blaise Barney
* LAST REVISED: 07/16/14
******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS     8

void *PrintHello(void *threadid)
{
   long taskid;
   sleep(1);
   taskid = *(long *)threadid;
   printf("Hello from thread %ld\n", taskid);
   pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc;
long t;

for(t=0;t<NUM_THREADS;t++) {
  printf("Creating thread %ld\n", t);
  rc = pthread_create(&threads[t], NULL, PrintHello, (void *) &t);
  if (rc) {
    printf("ERROR; return code from pthread_create() is %d\n", rc);
    exit(-1);
    }
   }

pthread_exit(NULL);
}

Joining and Detaching Threads

pthread_join (threadid,status)
pthread_detach (threadid)
pthread_attr_setdetachstate (attr,detachstate)
pthread_attr_getdetachstate (attr,detachstate)

joining是一種線程間同步的方式,pthread_join (threadid,status) 會blocks調用線程,直到threadid這個線程終止。

如圖

一個joining thread can match one pthread_join() call. It is a logical error to attempt multiple joins on the same thread.

還有另外兩種同步方法:mutex和條件變量,後面討論。

全部線程均可以Join嗎?

不是!一個thread建立的時候,它的一個屬性決定了它是joinable或者detached。只有joinable的線程才能夠join,detached的線程不能夠被join。POSIX標準指定, threads should be created as joinable.

那麼爲了建立一個joinable的thread,咱們應該在調用pthread_create的時候指定attr屬性。

  1. 建立一個pthread attribute variable of the pthread_attr_t data type
  2. Initialize the attribute variable with pthread_attr_init()
  3. Set the attribute detached status with pthread_attr_setdetachstate()
  4. When done, free library resources used by the attribute with pthread_attr_destroy()

Detaching

pthread_detach()能夠用來將建立時是joinable的threads編程detached,可是這種變化是不可逆的。沒有將建立時是detached的threads變成joinable的方法。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define NUM_THREADS    4

void *BusyWork(void *t)
{
    int i;
    long tid;
    double result=0.0;
    tid = (long)t;
    printf("Thread %ld starting...\n",tid);
    for (i=0; i<1000000; i++)
    {
        result = result + sin(i) * tan(i);
    }
    printf("Thread %ld done. Result = %e\n",tid, result);
    pthread_exit((void*) t);
}

int main (int argc, char *argv[])
{
    pthread_t thread[NUM_THREADS];
    pthread_attr_t attr;
    int rc;
    long t;
    void *status;

    /* Initialize and set thread detached attribute */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for(t=0; t<NUM_THREADS; t++) {
        printf("Main: creating thread %ld\n", t);
        rc = pthread_create(&thread[t], &attr, BusyWork, (void *)t);
        if (rc) {
            printf("ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }

    /* Free attribute and wait for the other threads */
    pthread_attr_destroy(&attr);
    for(t=0; t<NUM_THREADS; t++) {
        rc = pthread_join(thread[t], &status);
        if (rc) {
            printf("ERROR; return code from pthread_join() is %d\n", rc);
            exit(-1);
        }
        printf("Main: completed join with thread %ld having a status of %ld\n",t,(long)status);
    }

    printf("Main: program completed. Exiting.\n");
    pthread_exit(NULL);
}

printf("Main: completed join with thread %ld having a status of %ld\n" 必定是按照thread 0, 1, 2, 3的順序輸出的。個人感受,join就是調用方等待某個特定的線程結束。

注意join第二個參數是void **, 因此傳參的時候須要對void *再取&

Stack Management

pthread_attr_getstacksize (attr, stacksize)
pthread_attr_setstacksize (attr, stacksize)
pthread_attr_getstackaddr (attr, stackaddr)
pthread_attr_setstackaddr (attr, stackaddr)

POSIX標準不指示線程的棧大小,所以每一個標準的具體實現可能不同。

很容易超過默認的棧大小,而後程序就終止了。安全的程序不該該依賴於默認的棧大小,而應該使用 pthread_attr_setstacksize 爲每一個線程顯式地分配足夠的棧。

pthread_attr_getstackaddr (attr, stackaddr)pthread_attr_setstackaddr (attr, stackaddr) ,當須要將一個thread的stack必須放在某塊內存空間時有用。

Miscellaneous Routines

pthread_self ()//pthread_self returns the unique, system assigned thread ID of the calling thread. 
pthread_equal (thread1,thread2) //pthread_equal compares two thread IDs. If the two IDs are different 0 is returned, otherwise a non-zero value is returned.
pthread_once (once_control, init_routine)

pthread_self()返回指定線程的thread ID。

由於thread是pthread_t 的對象,對象內含一個線程id,因此咱們沒法用==來比較兩個pthread_t 的對象,能夠調用pthread_equa(thread1, thread2) 來比較兩個線程。

pthread_once executes the init_routine exactly once in a process. The first call to this routine by any thread in the process executes the given init_routine, without parameters. Any subsequent call will have no effect.

The once_control parameter is a synchronization control structure that requires initialization prior to calling pthread_once. For example:pthread_once_t once_control = PTHREAD_ONCE_INIT;

因此這個once_control究竟是什麼含義?

Mutex Variables

pthread_mutex_lock (mutex)
pthread_mutex_trylock (mutex)
pthread_mutex_unlock (mutex)

The pthread_mutex_lock() routine is used by a thread to acquire a lock on the specified mutex variable. If the mutex is already locked by another thread, this call will block the calling thread until the mutex is unlocked.

pthread_mutex_trylock() will attempt to lock a mutex. However, if the mutex is already locked, the routine will return immediately with a "busy" error code. This routine may be useful in preventing deadlock conditions, as in a priority-inversion situation.

pthread_mutex_unlock() will unlock a mutex if called by the owning thread. Calling this routine is required after a thread has completed its use of protected data if other threads are to acquire the mutex for their work with the protected data. An error will be returned if:

  1. If the mutex was already unlocked
  2. If the mutex is owned by another thread

Questions

  1. What is race condition?

    A race condition is any case where the results can be different depending on the order that processes arrive or are scheduled or depending on the order that specific competing instructions are executed.

    就是程序正確性正確性依賴於調度順序,調度順序不一樣或者進程到達時間不一樣,可能有不一樣的運行結果。

mutex使用步驟

Create and initialize a mutex variable

​ Several threads attempt to lock the mutex
​ Only one succeeds and that thread owns the mutex
​ The owner thread performs some set of actions
​ The owner unlocks the mutex
​ Another thread acquires the mutex and repeats the process
​ Finally the mutex is destroyed

Create and Destroy Mutexes

pthread_mutex_init (mutex,attr)//用來初始化pthread_mutex_t對象,這是動態方式。也能夠靜態方式:pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER

pthread_mutex_destroy (mutex)

pthread_mutexattr_init (attr)

pthread_mutexattr_destroy (attr)

attr對象表示mutex的一些屬性,Pthreads標準定義了3個可選的屬性:

Protocol: Specifies the protocol used to prevent priority inversions for a mutex.

​ Prioceiling: Specifies the priority ceiling of a mutex.
​ Process-shared: Specifies the process sharing of a mutex.
可是不是全部的實現都提供這3種屬性。

Condition Variables

條件變量是另外一種線程間同步的方式,mutexes經過控制線程對數據的訪問實現同步,條件變量容許基於數據的真實值進行同步。

條件變量能夠避免輪詢。

條件變量老是和mutex lock一塊兒使用。

閱讀材料

Pthread教程

相關文章
相關標籤/搜索