一道多线程编程面试题

一、有关线程操做的函数编程

#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;

}

编译运行能够获得符合要求的结果!

相关文章
相关标签/搜索