一道多線程編程面試題

一、有關線程操做的函數編程

#include <pthread.h>ubuntu

int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);多線程

int pthread_join (pthread_t tid, void ** status);函數

pthread_t pthread_self (void);測試

int pthread_detach (pthread_t tid);this

void pthread_exit (void *status);spa

pthread_create用於建立一個線程,成功返回0,不然返回Exxx(爲正數)。.net

pthread_t *tid:線程id的類型爲pthread_t,一般爲無符號整型,當調用pthread_create成功時,經過*tid指針返回。線程

const pthread_attr_t *attr:指定建立線程的屬性,如線程優先級、初始棧大小、是否爲守護進程等。可使用NULL來使用默認值,一般狀況下咱們都是使用默認值。指針

void *(*func) (void *):函數指針func,指定當新的線程建立以後,將執行的函數。

void *arg:線程將執行的函數的參數。若是想傳遞多個參數,請將它們封裝在一個結構體中。

pthread_join用於等待某個線程退出,成功返回0,不然返回Exxx(爲正數)。

pthread_t tid:指定要等待的線程ID

void ** status:若是不爲NULL,那麼線程的返回值存儲在status指向的空間中(這就是爲何status是二級指針的緣由!這種才參數也稱爲「值-結果」參數)。

pthread_self用於返回當前線程的ID。


pthread_detach用因而指定線程變爲分離狀態,就像進程脫離終端而變爲後臺進程相似。成功返回0,不然返回Exxx(爲正數)。變爲分離狀態的線程,若是線程退出,它的全部資源將所有釋放。而若是不是分離狀態,線程必須保留它的線程ID,退出狀態直到其它線程對它調用了pthread_join。


進程也是相似,這也是當咱們打開進程管理器的時候,發現有不少僵死進程的緣由!也是爲何必定要有僵死這個進程狀態。


pthread_exit用於終止線程,能夠指定返回值,以便其餘線程經過pthread_join函數獲取該線程的返回值。

void *status:指針線程終止的返回值。

知道了這些函數以後,咱們試圖來完成本文一開始的問題:


1)有一int型全局變量g_Flag初始值爲0;

2)在主線稱中起動線程1,打印「this is thread1」,並將g_Flag設置爲1

3)在主線稱中啓動線程2,打印「this is thread2」,並將g_Flag設置爲2

這3點很簡單嘛!!!不就是調用pthread_create建立線程。代碼以下:

/*

 * 1)有一int型全局變量g_Flag初始值爲0;

 * 2)在主線稱中起動線程1,打印「this is thread1」,並將g_Flag設置爲1

 * 3)在主線稱中啓動線程2,打印「this is thread2」,並將g_Flag設置爲2

 */

#include<stdio.h>

#include<stdlib.h>

#include<pthread.h>

#include<errno.h>

#include<unistd.h>


int g_Flag=0;


void* thread1(void*);

void* thread2(void*);


/*

 * when program is started, a single thread is created, called the initial thread or main thread.

 * Additional threads are created by pthread_create.

 * So we just need to create two thread in main().

 */

int main(int argc, char** argv)

{

    printf("enter main\n");

    pthread_t tid1, tid2;

    int rc1=0, rc2=0;

    rc2 = pthread_create(&tid2, NULL, thread2, NULL);

    if(rc2 != 0)

    printf("%s: %d\n",__func__, strerror(rc2));

    

    rc1 = pthread_create(&tid1, NULL, thread1, &tid2);

    if(rc1 != 0)

    printf("%s: %d\n",__func__, strerror(rc1));

    printf("leave main\n");

    exit(0);

}

/*

 * thread1() will be execute by thread1, after pthread_create()

 * it will set g_Flag = 1;

 */

void* thread1(void* arg)

{

    printf("enter thread1\n");

    printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());

    g_Flag = 1;

    printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());

    printf("leave thread1\n");

    pthread_exit(0);

}


/*

 * thread2() will be execute by thread2, after pthread_create()

 * it will set g_Flag = 2;

 */

void* thread2(void* arg)

{

    printf("enter thread2\n");

    printf("this is thread2, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());

    g_Flag = 2;

    printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self());

    printf("leave thread2\n");

    pthread_exit(0);

}

這樣就完成了1)、2)、3)這三點要求。編譯執行得以下結果:


netsky@ubuntu :~/workspace/pthead_test$ gcc -lpthread test.c


若是程序中使用到了pthread庫中的函數,除了要#include<pthread.h>,在編譯的時候還有加上-lpthread 選項。 

netsky@ubuntu :~/workspace/pthead_test$ ./a.out 

enter main 

enter thread2 

this is thread2, g_Flag: 0, thread id is 3079588720 

this is thread1, g_Flag: 2, thread id is 3079588720 

leave thread2 

leave main 

enter thread1 

this is thread1, g_Flag: 2, thread id is 3071196016 

this is thread1, g_Flag: 1, thread id is 3071196016 

leave thread1 

可是運行結果不必定是上面的,還有多是:


netsky@ubuntu :~/workspace/pthead_test$ ./a.out 

enter main 

leave main 

enter thread1 

this is thread1, g_Flag: 0, thread id is 3069176688 

this is thread1, g_Flag: 1, thread id is 3069176688 

leave thread1

或者是:

netsky@ubuntu :~/workspace/pthead_test$ ./a.out 

enter main 

leave main 

等等。這也很好理解由於,這取決於主線程main函數什麼時候終止,線程thread一、thread2是否可以來得急執行它們的函數。這也是多線程編程時要注意的問題,由於有可能一個線程會影響到整個進程中的全部其它線程!若是咱們在main函數退出前,sleep()一段時間,就能夠保證thread一、thread2來得及執行。


吸血蝙蝠Attention:你們確定已經注意到了,咱們在線程函數thread1()、thread2()執行完以前都調用了pthread_exit。若是我是調用exit()又或者是return會怎樣呢?本身動手試試吧!


pthread_exit()用於線程退出,能夠指定返回值,以便其餘線程經過pthread_join()函數獲取該線程的返回值。 

return是函數返回,只有線程函數return,線程纔會退出。 

exit是進程退出,若是在線程函數中調用exit,進程中的全部函數都會退出!


「4) 線程序1須要在線程2退出後才能退出」第4點也很容易解決,直接在thread1的函數退出以前調用pthread_join就OK了。


二、線程之間的互斥


     上面的代碼彷佛很好的解決了問題的前面4點要求,其實否則!!!由於g_Flag是一個全局變量,線程thread1和thread2能夠同時對它進行操做,須要對它進行加鎖保護,thread1和thread2要互斥訪問才行。下面咱們就介紹如何加鎖保護——互斥鎖。

互斥鎖:

使用互斥鎖(互斥)可使線程按順序執行。一般,互斥鎖經過確保一次只有一個線程執行代碼的臨界段來同步多個線程。互斥鎖還能夠保護單線程代碼。

互斥鎖的相關操做函數以下:


#include <pthread.h> 


int pthread_mutex_lock(pthread_mutex_t * mptr); 

int pthread_mutex_unlock(pthread_mutex_t * mptr); 

//Both return: 0 if OK, positive Exxx value on error

在對臨界資源進行操做以前須要pthread_mutex_lock先加鎖,操做完以後pthread_mutex_unlock再解鎖。並且在這以前須要聲明一個pthread_mutex_t類型的變量,用做前面兩個函數的參數。具體代碼見第5節。


三、線程之間的同步


第5點——主線程在檢測到g_Flag從1變爲2,或者從2變爲1的時候退出。就須要用到線程同步技術!線程同步須要條件變量。


條件變量:

使用條件變量能夠以原子方式阻塞線程,直到某個特定條件爲真爲止。條件變量始終與互斥鎖一塊兒使用。對條件的測試是在互斥鎖(互斥)的保護下進行的。

若是條件爲假,線程一般會基於條件變量阻塞,並以原子方式釋放等待條件變化的互斥鎖。若是另外一個線程更改了條件,該線程可能會向相關的條件變量發出信號,從而使一個或多個等待的線程執行如下操做:

喚醒

再次獲取互斥鎖

從新評估條件,在如下狀況下,條件變量可用於在進程之間同步線程:

線程是在能夠寫入的內存中分配的內存由協做進程共享

「使用條件變量能夠以原子方式阻塞線程,直到某個特定條件爲真爲止。」便可用到第5點,主線程main函數阻塞於等待g_Flag從1變爲2,或者從2變爲1。條件變量的相關函數以下:


#include <pthread.h>

 

int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr); 

int pthread_cond_signal(pthread_cond_t *cptr); 

//Both return: 0 if OK, positive Exxx value on error

pthread_cond_wait用於等待某個特定的條件爲真,pthread_cond_signal用於通知阻塞的線程某個特定的條件爲真了。在調用者兩個函數以前須要聲明一個pthread_cond_t類型的變量,用於這兩個函數的參數。

爲何條件變量始終與互斥鎖一塊兒使用,對條件的測試是在互斥鎖(互斥)的保護下進行的呢?由於「某個特性條件」一般是在多個線程之間共享的某個變量。互斥鎖容許這個變量能夠在不一樣的線程中設置和檢測。

一般,pthread_cond_wait只是喚醒等待某個條件變量的一個線程。若是須要喚醒全部等待某個條件變量的線程,須要調用:


int pthread_cond_broadcast (pthread_cond_t * cptr);

默認狀況下面,阻塞的線程會一直等待,知道某個條件變量爲真。若是想設置最大的阻塞時間能夠調用:


int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime);

若是時間到了,條件變量尚未爲真,仍然返回,返回值爲ETIME。


六、試題最終代碼


經過前面的介紹,咱們能夠輕鬆的寫出代碼了,以下所示:

/*

 是否熟悉POSIX多線程編程技術?如熟悉,編寫程序完成以下功能:

  1)有一int型全局變量g_Flag初始值爲0;

  2)在主線稱中起動線程1,打印「this is thread1」,並將g_Flag設置爲1

  3)在主線稱中啓動線程2,打印「this is thread2」,並將g_Flag設置爲2

  4)線程序1須要在線程2退出後才能退出

  5)主線程在檢測到g_Flag從1變爲2,或者從2變爲1的時候退出

   */

#include<stdio.h>

#include<stdlib.h>

#include<pthread.h>

#include<errno.h>

#include<unistd.h>


typedef void* (*fun)(void*);


int g_flag = 0;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

 

void *thread1_func(void *arg) {

    printf("this is thread1 id : %lu\n",pthread_self());

    pthread_mutex_lock(&mutex);

    if(g_flag == 2) {

        pthread_cond_signal(&cond);

    }

    g_flag = 1;

    pthread_mutex_unlock(&mutex);

     

    pthread_join(*(pthread_t *)arg,NULL);

    printf("leave thread1\n");

    pthread_exit(0);

}

 

void *thread2_func(void *arg) {

    printf("this is thread2 id : %lu\n",pthread_self());

    pthread_mutex_lock(&mutex);

    if(g_flag == 1) {

        pthread_cond_signal(&cond);

    }

    g_flag = 2;

    pthread_mutex_unlock(&mutex);

    printf("leave thread2\n");

    pthread_exit(0);

}

 

int main() {

    pthread_t p1,p2;

    /*建立線程1*/

    pthread_create(&p1,NULL,thread1_func,(void *)(&p2));

    /*若是加了sleep函數,線程1執行了一小部分,而後隨着線程2進行觸發主線程退出了*/

    /*建立線程2*/

    pthread_create(&p2,NULL,thread2_func,NULL);

    sleep(1);

    pthread_mutex_lock(&mutex);

    if(g_flag == 0) pthread_cond_wait(&cond,&mutex);

    pthread_mutex_unlock(&mutex);

    printf("leave main thread\n");

    return 0;

}

編譯運行能夠獲得符合要求的結果!

相關文章
相關標籤/搜索