——本文一个例子展开,介绍Linux下面线程的操做、多线程的同步和互斥。html
线程?为何有了进程还须要线程呢,他们有什么区别?使用线程有什么优点呢?还有多线程编程的一些细节问题,如线程之间怎样同步、互斥,这些东西将在本文中介绍。我在某QQ群里见到这样一道面试题:面试
是否熟悉POSIX多线程编程技术?如熟悉,编写程序完成以下功能:编程
1)有一int型全局变量g_Flag初始值为0;ubuntu
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的时候退出框架
咱们带着这题开始这篇文章,结束以后,你们就都会作了。本文的框架以下:less
进程是程序执行时的一个实例,即它是程序已经执行到何种程度的数据结构的聚集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。函数
线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程由几个线程组成(拥有不少相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其余的线程共享进程所拥有的所有资源。
"进程——资源分配的最小单位,线程——程序执行的最小单位"
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不一样执行路径。线程有本身的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,因此多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行而且又要共享某些变量的并发操做,只能用线程,不能用进程。
从上面咱们知道了进程与线程的区别,其实这些区别也就是咱们使用线程的理由。总的来讲就是:进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。(下面的内容摘自Linux下的多线程编程)
使用多线程的理由之一是和进程相比,它是一种很是"节俭"的多任务操做方式。咱们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,创建众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工做方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,并且,线程间彼此切换所需的时间也远远小于进程间切换所须要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,固然,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二是线程间方便的通讯机制。对不一样进程来讲,它们具备独立的数据空间,要进行数据的传递只能经过通讯的方式进行,这种方式不只费时,并且很不方便。线程则否则,因为同一进程下的线程之间共享数据空间,因此一个线程的数据能够直接为其它线程所用,这不只快捷,并且方便。固然,数据的共享也带来其余一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最须要注意的地方。
除了以上所说的优势外,不和进程比较,多线程程序做为一种多任务、并发的工做方式,固然有如下的优势:
=============================
从函数调用上来讲,进程建立使用fork()操做;线程建立使用clone()操做。Richard Stevens大师这样说过:
fork is expensive. Memory is copied from the parent to the child, all descriptors are duplicated in the child, and so on. Current implementations use a technique called copy-on-write, which avoids a copy of the parent's data space to the child until the child needs its own copy. But, regardless of this optimization, fork is expensive.
IPC is required to pass information between the parent and child after the fork. Passing information from the parent to the child before the fork is easy, since the child starts with a copy of the parent's data space and with a copy of all the parent's descriptors. But, returning information from the child to the parent takes more work.
Threads help with both problems. Threads are sometimes called lightweight processes since a thread is "lighter weight" than a process. That is, thread creation can be 10–100 times faster than process creation.
All threads within a process share the same global memory. This makes the sharing of information easy between the threads, but along with this simplicity comes the problem of synchronization.
=============================
#include <pthread.h> 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); void pthread_exit (void *status);
pthread_create用于建立一个线程,成功返回0,不然返回Exxx(为正数)。
pthread_join用于等待某个线程退出,成功返回0,不然返回Exxx(为正数)。
pthread_self用于返回当前线程的ID。
pthread_detach用因而指定线程变为分离状态,就像进程脱离终端而变为后台进程相似。成功返回0,不然返回Exxx(为正数)。变为分离状态的线程,若是线程退出,它的全部资源将所有释放。而若是不是分离状态,线程必须保留它的线程ID,退出状态直到其它线程对它调用了pthread_join。
进程也是相似,这也是当咱们打开进程管理器的时候,发现有不少僵死进程的缘由!也是为何必定要有僵死这个进程状态。
pthread_exit用于终止线程,能够指定返回值,以便其余线程经过pthread_join函数获取该线程的返回值。
知道了这些函数以后,咱们试图来完成本文一开始的问题:
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; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 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)); pthread_cond_wait(&cond, &mutex); 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()); pthread_mutex_lock(&mutex); if(g_Flag == 2) pthread_cond_signal(&cond); g_Flag = 1; printf("this is thread1, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); pthread_mutex_unlock(&mutex); pthread_join(*(pthread_t*)arg, NULL); 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()); pthread_mutex_lock(&mutex); if(g_Flag == 1) pthread_cond_signal(&cond); g_Flag = 2; printf("this is thread2, g_Flag: %d, thread id is %u\n",g_Flag, (unsigned int)pthread_self()); pthread_mutex_unlock(&mutex); printf("leave thread2\n"); pthread_exit(0); }
编译运行能够获得符合要求的结果!