日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux线程并不真正并行,Linux系统编程学习札记(十二)线程1

發布時間:2025/3/21 linux 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux线程并不真正并行,Linux系统编程学习札记(十二)线程1 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Linux系統編程學習筆記(十二)線程1

線程1:

線程和進程類似,但是線程之間能夠共享更多的信息。一個進程中的所有線程可以共享進程文件描述符和內存。

有了多線程控制,我們可以把我們的程序設計成為在一個進程同時做多個任務,每一個線程做一個獨立的任務,這種

方式可以有以下好處:

1、通過把每一個事件分配給一個線程處理,可以簡化異步事件處理的代碼。每一個線程可以用同步編程模型,而同步

編程要比異步編程簡單的多。

2、多個進程需要使用復雜的機制來共享內存和文件描述符。而線程可以自動共享同一內存地址空間和文件描述符。

3、有一些問題可以劃分以便提高這個程序的吞吐量。一個進程如果有多個任務,需要進行隱式的序列化任務,因為

只有一個線程控制。使用多線程控制,獨立的任務可以將每個任務分配一個線程。

4、交互式的進程可以改善響應時間,通過使用多線程將I/O和程序其他部分分開實現處理。

多線程不光可以在多核系統中得到并行的優勢,而且在單核系統中,也可以提高系統的吞吐量和響應時間,因為當一個線程

阻塞的時候,另一線程可以占有cpu執行。

線程有一些描述線程和執行環境的信息來表示它,包括線程ID,寄存器值的集合,棧,調度優先級和策略,信號的掩碼,errno

變量以及線程特有的一些結構。進程中各個線程共享進程的程序執行文本,程序的全局變量、堆內存、棧和文件描述符。

1、線程標志:

和進程一樣,每一個線程都有一個ID。和進程ID是全系統唯一不同,線程ID是在進程內唯一。進程id用pid_t類型來表示,是一個

非負的整數。線程ID由pthread_t數據類型代表,和進程一樣實現可能為一個結構,所以把pthread_t類型當做一個整數是不具有可

移植性,所以也沒有可移植的打印線程id的方法。這樣也需要一個函數來比較兩個線程的ID是否相同。

#include

int pthread_equal(pthread_t tid1, pthread_t tid2);

一個線程可以獲得使用pthread_self來獲得自己的線程id:

#include

pthread_t pthread_self();

這個方法 可以和pthread_equal配合使用,來識別被打上線程標記的數據結構。

2、進程創建:

通過調用pthread_create可以創建一個線程:

#include

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *),void *restrict arg);

創建成功tidp返回線程的id,attr為線程的屬性,新創建的進程會運行start_rtn函數,并傳入arg作為參數。如果想傳入多個參數到start_rtn

函數中,需要將它們存儲在一個結構體中,并把地址傳到arg中。失敗返回error code,而它們不設定errno。每個線程一個errno只是為了兼容

以前的函數而被使用的。多線程中,返回error code要比依賴于全局變量的errno清晰一些。

例子:創建一個線程,并打印進程id、新創建的線程id和主線程id。

#include

#include

#include

#include

pthread_t ntid;

void printids(const char *s){

pid_t pid;

pthread_t tid;

pid = getpid();

tid = pthread_self();

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

}

void * thr_fn(void *arg){

printids("new thread: ");

return ((void *) 0);

}

int main(void){

int err;

err = pthread_create(&ntid, NULL, thr_fn, NULL);

if(err != 0){

fprintf(stderr,"create pthread failed: %s",strerror(err));

exit(1);

}

printids("main thread: ");

sleep(1);

exit(0);

}

這個例子有兩個地方比較古怪,主要是為了處理主線程和新創建線程的競爭:1)主線程休眠,以防止主線程終止,導致真個進程的終止,新建的線程沒有機會

運行,我們后面介紹pthread_join可以避免這個。2)新的線程獲得它的線程id是通過調用pthread_self而不是讀取一個共享內存的變量或者傳遞的參數。這是因為

主線程不能安全的使用ntid,新的線程可能在調用pthread_create返回之前開始運行。

3、進程終止

如果進程內的任何一個線程調用exit,_Exit,_exit,整個進程就會終止。類似的如果信號的默認action是終止進程,那么一個發送到線程的信號會終止整個進程。

只終止一個線程有三種方式:

1、線程簡單的返回。返回值就是退出碼。

2、線程可以被進程中的另一個線程取消。

3、線程調用pthread_exit。

#include

void pthread_exit(void *rval_ptr);

rval_ptr是一個無類型的指針,和傳遞到進程的單個參數類似。這個指針可以被調用pthread_join的其他的線程得到。

#include

int pthread_join(pthread_t thread,void **rval_ptr);

調用pthread_join的線程會阻塞,直到指定的線程調用了pthread_exit,從start_rtn中返回,或者被取消。

如果線程簡單的返回,那么rval_ptr被設置成start_rtn的返回值,如果線程被取消,rval_ptr被設置成

PTHREAD_CANCELED。

通過調用pthread_join,我們自動將線程設置成為detached狀態,所以資源會被清除。如果線程已經處于detached狀態,

那么pthread_join就會失敗,返回EINVAL.

如果我們不關心線程的返回值,那么我們可以把rval_ptr設置為NULL。

#include

#include

#include

#include

void * thr_fun1(void *arg){

printf("Thread 1 returning...\n");

return ((void *)1);

}

void * thr_fun2(void *arg){

printf("Thread 2 exiting...\n");

pthread_exit((void *) 2);

}

int main(void){

int err;

pthread_t tid1,tid2;

void *tret;

err = pthread_create(&tid1,NULL,thr_fun1,NULL);

if(err != 0){

fprintf(stderr,"create thread1 failed: %s",strerror(err));

exit(1);

}

err = pthread_create(&tid2,NULL,thr_fun2,NULL);

if(err != 0){

fprintf(stderr,"create thread2 failed: %s",strerror(err));

exit(1);

}

err = pthread_join(tid1,&tret);

if( err != 0 ){

fprintf(stderr,"join thread1 failed: %s",strerror(err));

exit(1);

}

printf("Thread 1 exit code %d\n",(int)tret);

err = pthread_join(tid2,&tret);

if( err != 0 ){

fprintf(stderr,"join thread2 failed: %s",strerror(err));

exit(1);

}

printf("Thread 2 exit code %d\n",(int)tret);

exit(0);

}

無類型的指針傳遞給pthread_create和pthread_exit,使用它可以傳遞多個值,這個指針可以指向包含復雜的結構。但是需要注意這個結構在調用返回時仍然合法。如果

這個結構是在調用者的棧中,內存的內容在使用的可能時候已經被改變。比如一個線程申請在棧中申請了一個結構,然后將結構的指針傳遞給pthread_exit,接著這個

棧在調用thread_join的時候可能已經被銷毀。

例子:

#include

#include

#include

#include

struct foo{

int a,b,c,d;

};

void printfoo(const char *s, const struct foo *fp){

printf("%s",s);

printf(" structure at 0x%x\n",(unsigned) fp);

printf(" foo.a = %d\n", fp->a);

printf(" foo.b = %d\n", fp->b);

printf(" foo.c = %d\n", fp->c);

printf(" foo.d = %d\n", fp->d);

}

void *thr_fn1(void *arg){

struct foo foo = {1,2,3,4};

printfoo("thread 1:\n",&foo);

pthread_exit((void *)&foo);

}

void *thr_fn2(void *arg){

printf("thread 2: ID is %u\n",(unsigned)pthread_self());

pthread_exit((void *)0);

}

int main(void){

int err;

pthread_t tid1,tid2;

struct foo *fp;

err = pthread_create(&tid1,NULL,thr_fn1,NULL);

if(err != 0){

fprintf(stderr,"create thread1 failed: %s",strerror(err));

exit(1);

}

err = pthread_join(tid1,(void *)&fp);

if(err != 0){

fprintf(stderr,"thread_join failed: %s",strerror(err));

exit(1);

}

sleep(1);

printf("Parent starting second thread\n");

err = pthread_create(&tid2,NULL,thr_fn2,NULL);

if(err != 0){

fprintf(stderr,"create thread2 failed: %s",strerror(err));

exit(1);

}

sleep(1);

printfoo("Parent:\n",fp);

exit(0);

}

一個線程可以可以使用pthread_cancel來取消同一進程中的其他線程。

#include

int pthread_cancel(pthread_t tid);

在默認的條件下,pthread_cancle將會使由tid指定的線程像調用了pthread_exit(PTHREAD_CANCELED)一樣,但是一個線程可以選擇忽略和如果控制被取消。pthread_cancel

并不等待線程的終止,而只是發送一個請求。

一個線程可以注冊函數,當它終止的時候被調用,這個和進程使用atexit注冊函數,當進程終止的時候調用類似。這個函數比較出名的就是線程清理函數。一個線程可以

加入多個線程清理函數,這個清理函數保存在棧中,所以執行的順序和注冊的順序相反:

#include

void pthread_cleanup_push(void (*rtn)(void *), void arg);

void pthread_cleanup_pop(int execute);

pthread_cleanup_push來注冊清理函數rtn,這個函數有一個參數arg。但一下三種情形之一發生時,注冊的清理函數被執行:

1)調用pthread_exit

2)作為對取消線程請求(pthread_cancel)的響應。

3)以非0參數調用pthread_cleanup_pop。

如果pthread_cleanup_pop被傳遞0參數,則清除函數不會被調用,但是仍然會清除處于棧頂的清理函數。

一個限制是這兩個函數可能被實現為一個宏,所以在線程的同一作用域必須以匹配的成對出現。pthread_cleanup_push可能有{,而pthread_cleanup_pop可能有匹配這個

字符的}字符。

#include

#include

#include

#include

void cleanup(void *arg){

printf("cleanup: %s\n",(char *)arg);

}

void *thr_fn1(void *arg){

printf("thread 1 start\n");

pthread_cleanup_push(cleanup,"thread 1 first handler");

pthread_cleanup_push(cleanup,"thread 1 second handler");

printf("thread 1 push complete\n");

if(arg)

return ((void *)1);

pthread_cleanup_pop(1);

pthread_cleanup_pop(1);

return ((void *)1);

}

void *thr_fn2(void *arg){

printf("thread 2 start\n");

pthread_cleanup_push(cleanup,"thread 2 first handler");

pthread_cleanup_push(cleanup,"thread 2 second handler");

printf("thread 2 push complete\n");

if(arg){

pthread_exit((void *)2);

}

pthread_cleanup_pop(0);

pthread_cleanup_pop(0);

pthread_exit((void *) 2);

}

int main(void){

int err;

pthread_t tid1,tid2;

void *tret;

err = pthread_create(&tid1,NULL,thr_fn1,(void *)1);

if( err != 0){

fprintf(stderr,"create thread1 failed: %s",strerror(err));

exit(1);

}

err = pthread_create(&tid2,NULL,thr_fn2,(void *)2);

if(err != 0){

fprintf(stderr,"create thread 2 failed: %s",strerror(err));

exit(1);

}

err = pthread_join(tid1,&tret);

if(err != 0){

fprintf(stderr,"thread1 join failed: %s",strerror(err));

exit(1);

}

printf("thread 1 exit code %d\n",(int)tret);

err = pthread_join(tid2,&tret);

if(err != 0){

fprintf(stderr,"thread2 join failed: %s",strerror(err));

exit(1);

}

printf("thread 2 exit code %d\n",(int) tret);

exit(0);

}

如果線程從開始例程(start routine)中返回(by return statement),清理函數不會被調用。

線程的終止狀態,直到pthread_join被調用的時候才能得到。如果一個線程已經被detached,這個線程的空間將會被回收。pthread_join不能等待detached的線程,獲得其

終止狀態。pthread_join一個detached線程將會失敗,并返回EINVAL,我們可以通過pthread_detach來detach一個線程:

#include

int pthread_detach(pthread_t tid);

4、線程同步

當多個線程共享相同的內存時,我們需要保證每一個線程都看到一個一致的數據。如果一個線程的變量別的線程不能夠讀寫,或者變量時只讀的,那么不會有不一致的狀態。

然而一個線程可以修改一個變量,而其他的進程同時可以讀取或者修改它,我們需要同步線程來保證它們訪問變量,使用的是一個合法的值。

1)互斥量(Mutexes):

我們可以通過pthread提供的互斥量接口來保護我們的數據,確保每次只有一個線程訪問。一個mutex基本上是一個鎖,我們在訪問共享數據的時候設置(上鎖),在訪問完成

后釋放(解鎖)。當我們解鎖的互斥量的時候,當有多余一個的線程被阻塞時,所有阻塞在這個鎖的進程都被喚醒,變成可以運行的狀態,只有一個線程開始運行并設置鎖,

其他的看到互斥量仍然是被鎖定,繼續等待。

互斥量使用pthread_mutex_t數據類型,在我們使用一個互斥量變量時,我們必須先初始化它,可以初始化為PTHREAD_MUTEX_INITIALIZER(靜態初始化)或者調用

pthread_mutext_init,如果我們動態申請了互斥量,我們需要調用pthread_mutext_destory來銷毀它:

#include

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t * restrict attr);

int pthread_mutex_destory(pthread_mutex_t *mutex);

如果想使用默認的屬性來初始化互斥量,我們把attr設置為NULL。

例子:

1)靜態初始化

pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;

2)動態初始化:

int error;

pthread_mutex_t mylock;

if (error = pthread_mutex_init(&mylock, NULL))

fprintf(stderr, "Failed to initialize mylock:%s\n", strerror(error));

想給一個互斥量上鎖,我們調用pthread_mutex_lock.如果mutex已經上鎖,調用的線程將會被阻塞,直至信號量解鎖。要解鎖一個信號量,我們調用phtread_mutex_unlock

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

一個線程如果lock一個已經上鎖的互斥量,不想被阻塞,那么可以使用pthread_mutex_trylock,如果調用它的時候沒有被上鎖,就鎖住這個互斥量,如果已經上鎖,

就會失敗,并返回EBUSY。

例子:

我們使用mutex來保護數據結構:當多個進程需要訪問動態申請的結構,我們嵌入了引用計數,來保證知道所有線程都使用完它時,我們才釋放它。

#include

#include

struct foo{

int f_count;

pthread_mutex_t f_lock;

/* ...more stuff here... */

};

struct foo * foo_alloc(void){

struct foo *fp;

if((fp = malloc(sizeof(struct foo))) != NULL){

fp->f_count = 1;

if(pthread_mutex_init(&fp->f_lock,NULL) != 0){

free(fp);

return NULL;

}

}

return fp;

}

void foo_hold(struct foo *fp){

pthread_mutex_lock(&fp->f_lock);

fp->f_count++;

pthread_mutex_unlock(&fp->f_lock);

}

void foo_rele(struct foo *fp){

pthread_mutex_lock(&fp->f_lock);

if(--fp->f_count == 0){

pthread_mutex_unlock(&fp->f_lock);

pthread_mutex_destroy(&fp->f_lock);

free(fp);

}else{

pthread_mutex_unlock(&fp->f_lock);

}

}

2)讀寫鎖:

讀寫鎖也叫共享-排他鎖,和互斥量類似,除了它可以提供更高的并行性。使用mutex,它的狀態要么處于鎖住和未鎖狀態,只有一個線程可以上鎖。而讀

寫鎖有更多的狀態:在讀狀態鎖住,在寫狀態鎖住,未鎖住。只有一個線程可以獲得寫鎖,多個線程可以同時獲得讀鎖。當讀寫鎖處于寫鎖住狀態,所有

試圖上鎖的進程都被阻塞,當讀寫鎖處于讀鎖住狀態時,所有試圖上讀狀態的鎖成功,但是試圖獲得寫狀態鎖將會被阻塞,直到所有的讀進程都釋放讀狀

態鎖,此后來到試圖上讀鎖的線程也被阻塞。

讀寫鎖適合讀比寫頻繁情形。讀寫鎖和互斥量一樣也需要在使用前初始化,在釋放他們內存的時候銷毀。

#include

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock);

一個讀寫鎖可以調用pthread_rwlock_init來初始化,我們可以傳遞NULL作為attr的參數,這樣會使用讀寫鎖的默認屬性。

我們可以調用pthread_rwlock_destroy來清理,銷毀它所占的內存空間。

上鎖:

#include

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

實現上可能會對讀寫鎖中讀模式的鎖鎖住次數有一定的限制,所以我們需要檢查返回值,以確定是否成功。而其他的兩個函數

會返回錯誤,但是只要我們的鎖設計的恰當,我們可以不必做檢查。

Single UNIX規范另外兩個讀寫鎖原語:

#include

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

當鎖成功獲取時,返回0,否則返回EBUSY。這兩個函數使用在一個上鎖的結構不能夠保證產生死鎖的時候,它可以避免死鎖。

3)條件變量:

條件變量時另一中線程同步的機制,允許線程以無競爭的方式等待特定的條件發生。條件變量本身需要互斥量的保護,線程在改變條件前必須首先鎖住互斥量,

且只有在鎖住互斥量以后才能計算條件。條件變量使用之前必須首先進行初始化,pthread_cond_t數據類型代表的條件變量可以用兩種方式初始化。

可以把常量PTHREAD_COND_INITIALIZER賦給靜態分配的條件變量,但是如果條件變量是動態分配的,可以使用pthread_cond_init函數進行初始化。

在釋放底層的內存空間前,可以使用pthread_mutex_destroy函數對條件變量進行銷毀。除非需要創建一個非默認屬性的條件變量,否則pthread_cond_init

函數的attr參數可以設置為NULL。

#include

int pthread_cond_init(pthread_cond_t *restrict cond,

pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

成功返回0,失敗返回錯誤碼。使用pthread_cond_wait等待條件變為真,如果在給定時間內條件不能滿足,那么會生成一個代表出錯碼的返回值。

調用者需要把鎖住的互斥量傳給pthread_cond_wait對條件進行保護。函數把調用線程放到等待條件的線程列表上,然后對互斥量解鎖,這兩個操作

是原子操作。當pthread_cond_wait返回時,互斥量再次被鎖住。

#include

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_timewait(pthread_cond_t * restict cond, pthread_mutex_t *restrict mutex,const struct timespec * restrict timeout);

pthread_cond_timedwait函數的工作方式與pthread_cond_wait函數相似。timeout值指定了等待的時間,它通過timespec結構指定。時間值用秒數或者

分秒數表示,分秒數的單位是納秒。時間值是一個絕對數而不是相對數??梢允褂胓ettimeofday獲取用timeval結構表示的當前時間,然后把這個時間加

上要等待的時間轉換成timespec結構:

void maketimeout(struct timespec *tsp, long minutes){

struct timeval now;

/* get the current time */

gettimeofday(&now);

tsp->tv_sec = now.tv_sec;

tsp->tv_nsec = now.tv_usec * 10000; /* usec to nsec */

tsp->tv_sec += minutes * 60;

}

如果時間值到了但是條件還沒有出現,pthread_cond_timedwait將重新獲取互斥量,然后返回錯誤ETIMEDOUT。從pthread_cond_wait或者pthread_cond_timedwait

調用成功返回時,線程需要重新計算條件,因為其它線程可能已經在運行并改變了條件。pthread_cond_signal函數將喚醒等待該條件的某個線程,而pthread_cond_broadcast

函數將喚醒等待該條件的所有線程。必須注意一定要在改變條件狀態以后再喚醒等待線程

#include

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

例子:

#include

struct msg {

struct msg *m_next;

/* ... more stuff here ... */

};

struct msg *workq;

pthread_cond_t qready = PTHREAD_COND_INITIALIZER; /*初始化條件變量*/

pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; /*初始化互斥量*/

void process_msg(void)

{

struct msg *mp;

for (;;) {

pthread_mutex_lock(&qlock); /*條件本身由互斥量保護*/

while (workq == NULL) /*wait返回后要重新檢查條件*/

pthread_cond_wait(&qready, &qlock); /*wait期間釋放互斥量,返回時再次鎖住*/

mp = workq;

workq = mp->m_next;

pthread_mutex_unlock(&qlock); /*真正釋放互斥量*/

/* now process the message mp */

}

}

void enqueue_msg(struct msg *mp)

{

pthread_mutex_lock(&qlock); /*修改條件前鎖住互斥量*/

mp->m_next = workq;

workq = mp;

pthread_mutex_unlock(&qlock);

pthread_cond_signal(&qready); /*喚醒等待線程時不需要占有互斥量*/

/*如果希望在wait返回時不用再檢查條件,就需要在喚醒時占有互斥量*/

}

參考:

《Advanced programming in Unix Environment 2ed》第11章

1 樓

zhu_jinlong

2010-05-14

博主寫得好!繼續寫下去!

總結

以上是生活随笔為你收集整理的linux线程并不真正并行,Linux系统编程学习札记(十二)线程1的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。