程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 淺析C++編程傍邊的線程

淺析C++編程傍邊的線程

編輯:關於C++

淺析C++編程傍邊的線程。本站提示廣大學習愛好者:(淺析C++編程傍邊的線程)文章只能為提供參考,不一定能成為您想要的結果。以下是淺析C++編程傍邊的線程正文


線程的概念

C++中的線程的Text Segment和Data Segment都是同享的,假如界說一個函數,在各線程中都可以挪用,假如界說一個全局變量,在各線程中都可以拜訪到。除此以外,各線程還同享以下過程資本和情況:

  •     文件描寫符
  •     每種旌旗燈號的處置方法
  •     以後任務目次
  •     用戶id和組id

然則,有些資本是每一個線程各有一份的:

  •     線程id
  •     高低文,包含各類存放器的值、法式計數器和棧指針
  •     棧空間
  •     errno變量
  •     旌旗燈號屏障字
  •     調劑優先級

我們將要進修的線程庫函數是由POSIX尺度界說的,稱為POSIX thread或pthread。
線程掌握
創立線程

創立線程的函數原型以下:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

前往值:勝利前往0,掉敗前往毛病號。

在一個線程中挪用pthread_create()創立新的線程後,以後線程從pthread_create()前往持續往下履行,而新的線程所履行的代碼由我們傳給pthread_create的函數指針start_routine決議。start_routine函數吸收一個參數,是經由過程pthread_create的arg參數傳遞給它的,該參數類型為void*,這個指針按甚麼類型說明由挪用者本身界說。start_routine的前往值類型也是void *,這個指針的寄義異樣由挪用者本身界說。start_routine前往時,這個線程就加入了,其它線程可以挪用pthread_join獲得start_routine的前往值。

pthread_create勝利前往後,新創立的線程的id被填寫到thread參數所指向的內存單位。我們曉得過程id的類型是pid_t,每一個過程的id在全部體系中是獨一的,挪用getpid可以獲得以後過程的id,是一個正整數值。線程id的類型是thread_t,它只在以後過程中包管是獨一的,在分歧的體系中thread_t這個類型有分歧的完成,它能夠是一個整數值,也能夠是一個構造體,也能夠是一個地址,所以不克不及簡略確當成整數用printf打印,挪用pthread_self可以獲得以後線程的id。

我們先來寫一個簡略的例子:

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

pthread_t ntid;

void printids(const void *t)
{
    char *s = (char *)t;
  pid_t   pid;
  pthread_t tid;

  pid = getpid();
  tid = pthread_self();
  printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
      (unsigned int)tid, (unsigned int)tid);
}

void *thr_fn(void *arg)
{
  printids(arg);
  return NULL;
}

int main(void)
{
  int err;

  err = pthread_create(&ntid, NULL, thr_fn, (void *)"Child Process:");
  if (err != 0) {
    fprintf(stderr, "can't create thread: %s\n", strerror(err));
    exit(1);
  }
  printids("main thread:");
  sleep(1);

  return 0;
}


編譯履行成果以下:

g++ thread.cpp -o thread -lpthread
./thread
main thread: pid 21046 tid 3612727104 (0xd755d740)
Child Process: pid 21046 tid 3604444928 (0xd6d77700)

從成果可以曉得,thread_t類型是一個地址值,屬於統一過程的多個線程挪用getpid可以獲得雷同的過程號,而挪用pthread_self獲得的線程號各不雷同。

假如隨意率性一個線程挪用了exit或_exit,則全部過程的一切線程都終止,因為從main函數return也相當於挪用exit,為了避免新創立的線程還沒有獲得履行就終止,我們在main函數return之前延時1秒,這只是一種權宜之計,即便主線程期待1秒,內核也紛歧定會調劑新創立的線程履行,接上去,我們進修一下比擬好的處理辦法。
終止線程

假如須要只終止某個線程而不是終止全部過程,可以有三種辦法:

  1.     從線程函數return。這類辦法對主線程不順應,從main函數return相當於挪用exit。
  2.     一個線程可以挪用pthread_cancel終止統一個過程中的另外一個線程。
  3.     線程可以挪用pthread_exit終止本身。
  4. 這裡重要引見pthread_exit和pthread_join的用法。

    #include <pthread.h>
    
    void pthread_exit(void *value_ptr);
    
    

    value_ptr是void*類型,和線程函數前往值的用法一樣,其它線程可以挪用pthread_join獲得這個指針。
    須要留意,pthread_exit或許return前往的指針所指向的內存單位必需是全局的或許是用malloc分派的,不克不及在線程函數的棧上分派,由於當其它線程獲得這個前往指針時線程函數曾經加入了。

    #include <pthread.h>
    
    int pthread_join(pthread_t thread, void **value_ptr);
    
    

    前往值:勝利前往0,掉敗前往毛病號。

    挪用該函數的線程將掛起期待,直到id為thread的線程終止。thread線程以分歧的辦法終止,經由過程pthread_join獲得的終止狀況是分歧的,總結以下:

    •     假如thread線程經由過程return前往,value_ptr所指向的單位裡寄存的是thread線程函數的前往值。
    •     假如thread線程被其余線程挪用pthread_cancel異常終止失落,value_ptr所指向的單位寄存的是常數PTHREAD_CANCELED。
    •     假如thread線程是本身挪用pthread_exit終止的,value_ptr所指向的單位寄存的是傳給pthread_exit的參數。

    假如對thread線程的終止狀況不感興致,可以傳NULL給value_ptr參數。參考代碼以下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void* thread_function_1(void *arg)
    {
      printf("thread 1 running\n");
      return (void *)1;
    }
    
    void* thread_function_2(void *arg)
    {
      printf("thread 2 exiting\n");
      pthread_exit((void *) 2);
    }
    
    void* thread_function_3(void* arg)
    {
      while (1) {
        printf("thread 3 writeing\n");
        sleep(1);
      }
    }
    
    
    int main(void)
    {
      pthread_t tid;
      void *tret;
    
      pthread_create(&tid, NULL, thread_function_1, NULL);
      pthread_join(tid, &tret);
      printf("thread 1 exit code %d\n", *((int*) (&tret)));
    
      pthread_create(&tid, NULL, thread_function_2, NULL);
      pthread_join(tid, &tret);
      printf("thread 2 exit code %d\n", *((int*) (&tret)));
    
      pthread_create(&tid, NULL, thread_function_3, NULL);
      sleep(3);
      pthread_cancel(tid);
      pthread_join(tid, &tret);
      printf("thread 3 exit code %d\n", *((int*) (&tret)));
    
      return 0;
    }
    
    

    運轉成果是:

    thread 1 running
    thread 1 exit code 1
    thread 2 exiting
    thread 2 exit code 2
    thread 3 writeing
    thread 3 writeing
    thread 3 writeing
    thread 3 exit code -1
    
    


    可見,Linux的pthread庫中常數PTHREAD_CANCELED的值是-1.可以在頭文件pthread.h中找到它的界說:

    #define PTHREAD_CANCELED ((void *) -1)
    
    


    線程間同步

    多個線程同時拜訪同享數據時能夠會抵觸,例如兩個線程都要把某個全局變量增長1,這個操作在某平台上須要三條指令能力完成:

    •     從內存讀變量值到存放器。
    •     存放器值加1.
    •     將存放器的值寫回到內存。

    這個時刻很輕易湧現兩個過程同時操作存放器變量值的情形,招致終究成果不准確。

    處理的方法是引入互斥鎖(Mutex, Mutual Exclusive Lock),取得鎖的線程可以完成“讀-修正-寫”的操作,然後釋放鎖給其它線程,沒有取得鎖的線程只能期待而不克不及拜訪同享數據,如許,“讀-修正-寫”的三步操作構成一個原子操作,要不都履行,要不都不履行,不會履行到中央被打斷,也不會在其它處置器上並行做這個操作。

    Mutex用pthread_mutex_t類型的變量表現,可以如許初始化和燒毀:

    #include <pthread.h>
    
    int pthread_mutex_destory(pthread_mutex_t *mutex);
    int pthread_mutex_int(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
    pthread_mutex_t mutex = PTHEAD_MUTEX_INITIALIZER;
    
    


    前往值:勝利前往0,掉敗前往毛病號。

    用pthread_mutex_init函數初始化的Mutex可以用pthread_mutex_destroy燒毀。假如Mutex變量是靜態分派的(全局變量或static變量),也能夠用宏界說PTHREAD_MUTEX_INITIALIZER來初始化,相當於用pthread_mutex_init初始化而且attr參數為NULL。Mutex的加鎖息爭鎖操作可以用以下函數:

    #include <pthread.h>
    
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
    


    前往值:勝利前往0,掉敗前往毛病號。

    一個線程可以挪用pthread_mutex_lock取得Mutex,假如這時候另外一個線程曾經挪用pthread_mutex_lock取得了該Mutex,則以後線程須要掛起期待,直到另外一個線程挪用pthread_mutex_unlock釋放Mutex,以後線程被叫醒,能力取得該Mutex並持續履行。

    我們用Mutex處理下面說的兩個線程同時對全局變量+1能夠招致雜亂的成績:

    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #define NLOOP 5000
    
    int counter;
    pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
    
    void *do_add_process(void *vptr)
    {
      int i, val;
    
      for (i = 0; i < NLOOP; i ++) {
        pthread_mutex_lock(&counter_mutex);
        val = counter;
        printf("%x:%d\n", (unsigned int)pthread_self(), val + 1);
        counter = val + 1;
        pthread_mutex_unlock(&counter_mutex);
      }
    
      return NULL;
    }
    
    int main()
    {
      pthread_t tida, tidb;
    
      pthread_create(&tida, NULL, do_add_process, NULL);
      pthread_create(&tidb, NULL, do_add_process, NULL);
    
      pthread_join(tida, NULL);
      pthread_join(tidb, NULL);
    
      return 0;
    }
    
    

    如許,每次運轉都能顯示到10000。假如去失落鎖機制,能夠就會有成績。這個機制相似於Java的synchronized塊機制。
    Condition Variable

    線程間的同步還有如許一種情形:線程A須要等某個前提成立能力持續往下履行,如今這個前提不成立,線程A就壅塞期待,而線程B在履行進程中使這個前提成立了,就叫醒線程A持續履行。在pthread庫中經由過程前提變量(Conditiion Variable)來壅塞期待一個前提,或許叫醒期待這個前提的線程。Condition Variable用pthread_cond_t類型的變量表現,可以如許初始化和燒毀:

    #include <pthread.h>
    
    int pthread_cond_destory(pthread_cond_t *cond);
    int pthread_cond_init(pthead_cond_t *cond, const pthread_condattr_t *attr);
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
    

    前往值:勝利前往0,掉敗前往毛病號。

    和Mutex的初始化和燒毀相似,pthread_cond_init函數初始化一個Condition Variable,attr參數為NULL則表現缺省屬性,pthread_cond_destroy函數燒毀一個Condition Variable。假如Condition Variable是靜態分派的,也能夠用宏界說PTHEAD_COND_INITIALIZER初始化,相當於用pthread_cond_init函數初始化而且attr參數為NULL。Condition Variable的操作可以用以下函數:

    #include <pthread.h>
    
    int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    int pthread_cond_broadcast(pthread_cond_t *cond);
    int pthread_cond_signal(pthread_cond_t *cond);
    
    


    可見,一個Condition Variable老是和一個Mutex搭配應用的。一個線程可以挪用pthread_cond_wait在一個Condition Variable上壅塞期待,這個函數做以下三步操作:

    1.     釋放Mutex。
    2.     壅塞期待。
    3.     當被叫醒時,從新取得Mutex並前往。
    4. pthread_cond_timedwait函數還有一個額定的參數可以設定期待超時,假如達到了abstime所指定的時辰依然沒有其余線程來叫醒以後線程,就前往ETIMEDOUT。一個線程可以挪用pthread_cond_signal叫醒在某個Condition Variable上期待的另外一個線程,也能夠挪用pthread_cond_broadcast叫醒在這個Condition Variable上期待的一切線程。

      上面的法式演示了一個臨盆者-花費者的例子,臨盆者臨盆一個構造體串在鏈表的表頭上,花費者從表頭取走構造體。

      #include <stdio.h>
      #include <stdlib.h>
      #include <pthread.h>
      #include <unistd.h>
      
      struct msg {
        struct msg *next;
        int num;
      };
      
      struct msg *head;
      pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
      pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
      
      void* consumer(void *p)
      {
        struct msg *mp;
      
        for(;;) {
          pthread_mutex_lock(&lock);
          while (head == NULL) {
            pthread_cond_wait(&has_product, &lock);
          }
          mp = head;
          head = mp->next;
          pthread_mutex_unlock(&lock);
          printf("Consume %d\n", mp->num);
          free(mp);
          sleep(rand() % 5);
        }
      }
      
      void* producer(void *p)
      {
        struct msg *mp;
      
        for(;;) {
          mp = (struct msg *)malloc(sizeof(*mp));
          pthread_mutex_lock(&lock);
          mp->next = head;
          mp->num = rand() % 1000;
          head = mp;
          printf("Product %d\n", mp->num);
          pthread_mutex_unlock(&lock);
          pthread_cond_signal(&has_product);
          sleep(rand() % 5);
        }
      }
      
      int main()
      {
        pthread_t pid, cid;
        srand(time(NULL));
      
        pthread_create(&pid, NULL, producer, NULL);
        pthread_create(&cid, NULL, consumer, NULL);
      
        pthread_join(pid, NULL);
        pthread_join(cid, NULL);
      
        return 0;
      }
      
      

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved