1. 程式人生 > >POSIX 多線程編程及理解

POSIX 多線程編程及理解

等待 source awake created http associate def span variables

最近開發基於ZYNQ的嵌入式linux程序,涉及到多線程使用,將一些內容整理如下:

POSIX多線程編程最為基礎和重要的可以分為兩部分:

  1. 線程操作-Thread Management
  2. 線程同步-Synchronization

線程同步主要是由於線程共享同一進程裏的資源,因而需要程序員自己對資源進行同步來避免競爭產生

1.線程操作

pthread_create (thread,attr,start_routine,arg) 

pthread_exit (status)

pthread_cancel (thread)

pthread_attr_init (attr)

pthread_attr_destroy (attr) 

具體函數使用見參考文獻1,現將參考文獻1中示例代碼貼出

/******************************************************************************
* FILE: hello.c
* DESCRIPTION:
*   A "hello world" Pthreads program.  Demonstrates thread creation and
*   termination.
* AUTHOR: Blaise Barney
* LAST REVISED: 08/09/11
*****************************************************************************
*/ #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); }

2.線程同步

線程同步POSIX主要提供兩種結構:

  • 互斥量
  • 條件變量

互斥量的使用較為簡單,具體參見參考文獻1,現貼出參考文獻1中示例代碼

 #include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>

 /*   
 The following structure contains the necessary information  
 to allow the function "dotprod" to access its input data and 
 place its output into the structure.  
 */

 typedef struct 
  {
    double      *a;
    double      *b;
    double     sum; 
    int     veclen; 
  } DOTDATA;

 /* Define globally accessible variables and a mutex */

 #define NUMTHRDS 4
 #define VECLEN 100
    DOTDATA dotstr; 
    pthread_t callThd[NUMTHRDS];
    pthread_mutex_t mutexsum;

 /*
 The function dotprod is activated when the thread is created.
 All input to this routine is obtained from a structure 
 of type DOTDATA and all output from this function is written into
 this structure. The benefit of this approach is apparent for the 
 multi-threaded program: when a thread is created we pass a single
 argument to the activated function - typically this argument
 is a thread number. All  the other information required by the 
 function is accessed from the globally accessible structure. 
 */  

 void *dotprod(void *arg)
 {

    /* Define and use local variables for convenience */

    int i, start, end, len ;
    long offset;
    double mysum, *x, *y;
    offset = (long)arg;
     
    len = dotstr.veclen;
    start = offset*len;
    end   = start + len;
    x = dotstr.a;
    y = dotstr.b;

    /*
    Perform the dot product and assign result
    to the appropriate variable in the structure. 
    */

    mysum = 0;
    for (i=start; i<end ; i++) 
     {
       mysum += (x[i] * y[i]);
     }

    /*
    Lock a mutex prior to updating the value in the shared
    structure, and unlock it upon updating.
    */
    pthread_mutex_lock (&mutexsum);
    dotstr.sum += mysum;
    pthread_mutex_unlock (&mutexsum);

    pthread_exit((void*) 0);
 }

 /* 
 The main program creates threads which do all the work and then 
 print out result upon completion. Before creating the threads,
 the input data is created. Since all threads update a shared structure, 
 we need a mutex for mutual exclusion. The main thread needs to wait for
 all threads to complete, it waits for each one of the threads. We specify
 a thread attribute value that allow the main thread to join with the
 threads it creates. Note also that we free up handles when they are
 no longer needed.
 */

 int main (int argc, char *argv[])
 {
    long i;
    double *a, *b;
    void *status;
    pthread_attr_t attr;  

    /* Assign storage and initialize values */
    a = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double));
    b = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double));
   
    for (i=0; i<VECLEN*NUMTHRDS; i++)
      {
      a[i]=1.0;
      b[i]=a[i];
      }

    dotstr.veclen = VECLEN; 
    dotstr.a = a; 
    dotstr.b = b; 
    dotstr.sum=0;

    pthread_mutex_init(&mutexsum, NULL);
         
    /* Create threads to perform the dotproduct  */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for(i=0; i<NUMTHRDS; i++)
    {
    /* 
    Each thread works on a different set of data. The offset is specified 
    by ‘i‘. The size of the data for each thread is indicated by VECLEN.
    */
    pthread_create(&callThd[i], &attr, dotprod, (void *)i);
    }

    pthread_attr_destroy(&attr);

    /* Wait on the other threads */
    for(i=0; i<NUMTHRDS; i++)
       {
       pthread_join(callThd[i], &status);
       }

    /* After joining, print out the results and cleanup */
    printf ("Sum =  %f \n", dotstr.sum);
    free (a);
    free (b);
    pthread_mutex_destroy(&mutexsum);
    pthread_exit(NULL);
 }   

條件變量和互斥量一般同時使用(原因下面做分析),使用方法參見參考文獻1,現貼出參考文獻1示例代碼

/******************************************************************************
* FILE: condvar.c
* DESCRIPTION:
*   Example code for using Pthreads condition variables.  The main thread
*   creates three threads.  Two of those threads increment a "count" variable,
*   while the third thread watches the value of "count".  When "count" 
*   reaches a predefined limit, the waiting thread is signaled by one of the
*   incrementing threads. The waiting thread "awakens" and then modifies
*   count. The program continues until the incrementing threads reach
*   TCOUNT. The main program prints the final value of count.
* SOURCE: Adapted from example code in "Pthreads Programming", B. Nichols
*   et al. O‘Reilly and Associates. 
* LAST REVISED: 03/07/17  Blaise Barney
******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_THREADS  3
#define TCOUNT 10
#define COUNT_LIMIT 12

int     count = 0;
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;

void *inc_count(void *t) 
{
  int i;
  long my_id = (long)t;

  for (i=0; i < TCOUNT; i++) {
    pthread_mutex_lock(&count_mutex);
    count++;

    /* 
    Check the value of count and signal waiting thread when condition is
    reached.  Note that this occurs while mutex is locked. 
    */
    if (count == COUNT_LIMIT) {
      printf("inc_count(): thread %ld, count = %d  Threshold reached. ",
             my_id, count);
      pthread_cond_signal(&count_threshold_cv);
      printf("Just sent signal.\n");
      }
    printf("inc_count(): thread %ld, count = %d, unlocking mutex\n", 
       my_id, count);
    pthread_mutex_unlock(&count_mutex);

    /* Do some work so threads can alternate on mutex lock */
    sleep(1);
    }
  pthread_exit(NULL);
}

void *watch_count(void *t) 
{
  long my_id = (long)t;

  printf("Starting watch_count(): thread %ld\n", my_id);

  /*
  Lock mutex and wait for signal.  Note that the pthread_cond_wait routine
  will automatically and atomically unlock mutex while it waits. 
  Also, note that if COUNT_LIMIT is reached before this routine is run by
  the waiting thread, the loop will be skipped to prevent pthread_cond_wait
  from never returning.
  */
  pthread_mutex_lock(&count_mutex);
  while (count < COUNT_LIMIT) {
    printf("watch_count(): thread %ld Count= %d. Going into wait...\n", my_id,count);
    pthread_cond_wait(&count_threshold_cv, &count_mutex);
    printf("watch_count(): thread %ld Condition signal received. Count= %d\n", my_id,count);
    }
  printf("watch_count(): thread %ld Updating the value of count...\n", my_id);
  count += 125;
  printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
  printf("watch_count(): thread %ld Unlocking mutex.\n", my_id);
  pthread_mutex_unlock(&count_mutex);
  pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
  int i, rc; 
  long t1=1, t2=2, t3=3;
  pthread_t threads[3];
  pthread_attr_t attr;

  /* Initialize mutex and condition variable objects */
  pthread_mutex_init(&count_mutex, NULL);
  pthread_cond_init (&count_threshold_cv, NULL);

  /* For portability, explicitly create threads in a joinable state */
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
  pthread_create(&threads[0], &attr, watch_count, (void *)t1);
  pthread_create(&threads[1], &attr, inc_count, (void *)t2);
  pthread_create(&threads[2], &attr, inc_count, (void *)t3);

  /* Wait for all threads to complete */
  for (i = 0; i < NUM_THREADS; i++) {
    pthread_join(threads[i], NULL);
  }
  printf ("Main(): Waited and joined with %d threads. Final value of count = %d. Done.\n", 
          NUM_THREADS, count);

  /* Clean up and exit */
  pthread_attr_destroy(&attr);
  pthread_mutex_destroy(&count_mutex);
  pthread_cond_destroy(&count_threshold_cv);
  pthread_exit (NULL);

}

初次接觸條件變量很容易疑惑為什麽條件變量一定要與互斥量一同使用,很多人認為互斥量是用來保證條件變量的原子性的,其實這是沒有真正理解條件變量設計初衷導致的。

條件變量設計也是為了各個線程同步數據,並非單單做等待。如果不使用條件變量,程序就需要在代碼關鍵區內不斷進行查詢操作,浪費CPU時間片,為此設計了條件變量來消除查詢操作的代價。因此把條件變量看做互斥量的一個補充就好理解為什麽兩者要同時出現了,而不是把互斥量看做條件變量的補充。

也可以理解為線程數據同步都是靠互斥量進行的,條件變量只是做了一個消息通知機制而已。如果僅僅為了等待,而不存在數據保護,條件變量其實無需互斥量。

參考文獻2也做了深入分析,可作參考

參考文獻:

1.https://computing.llnl.gov/tutorials/pthreads/

2.http://www.cnblogs.com/Dahaka/archive/2012/02/19/2358528.html

POSIX 多線程編程及理解