淺析C++編程傍邊的線程。本站提示廣大學習愛好者:(淺析C++編程傍邊的線程)文章只能為提供參考,不一定能成為您想要的結果。以下是淺析C++編程傍邊的線程正文
線程的概念
C++中的線程的Text Segment和Data Segment都是同享的,假如界說一個函數,在各線程中都可以挪用,假如界說一個全局變量,在各線程中都可以拜訪到。除此以外,各線程還同享以下過程資本和情況:
然則,有些資本是每一個線程各有一份的:
我們將要進修的線程庫函數是由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秒,內核也紛歧定會調劑新創立的線程履行,接上去,我們進修一下比擬好的處理辦法。
終止線程
假如須要只終止某個線程而不是終止全部過程,可以有三種辦法:
這裡重要引見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線程的終止狀況不感興致,可以傳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,這個操作在某平台上須要三條指令能力完成:
這個時刻很輕易湧現兩個過程同時操作存放器變量值的情形,招致終究成果不准確。
處理的方法是引入互斥鎖(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上壅塞期待,這個函數做以下三步操作:
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; }