本文转自吴秦 http://www.cnblogs.com/skynet/archive/2010/10/30/1865267.HTML
从一个简单的程序出发进行学习:
(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时才能退出
1.进程和线程
进程是程序执行时的一个实例,即它是程序已经执行到何种程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。
线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
综上我们可以看出:进程是资源分配的最小单位 线程是程序执行的最小单位
进程有自己独立的地址空间,在保护模式下不会对其他进程产生影响,而进程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程.
2.使用线程的理由
区别总的来说在于:进程有自己独立的地址空间,而线程没有单独的地址空间,同一进程内的线程共享进程的地址空间
使用多线程的理由:(1)“节俭”的多任务操作方式 (2)线程之间方便的通信机制(共享数据空间)
此外别的优点包括:(1)提高应用程序相应(2)使多CPU系统更加有效,操作系统会保证线程数不大于CPU数目时,不同的线程运行于不同的CPU上
从函数调用上来说:进程创建使用fork(),线程创建使用clone()操作
3.有关线程操作的函数
#include <pthread.h>
int pthread_create(pthread_t*tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);
int pthread_join (pthread_ttid, void **status);
pthread_t pthread_self (void);
int pthread_detach (pthread_ttid);
void pthread_exit (void*status);
pthread_create用于创建一个线程,成功返回0,否则返回Exxx(为正数)。
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:指针线程终止的返回值。
4.一个简单的示例
如果只是实现文章开始的前三个功能,那么很简单。
/ *
* 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>
#include<string.h>
int g_Flag=0;
void* thread1(void*);
void* thread2(void*);
/*
* when program is started, a single thread iscreated, called the initial thread or main thread.
* Additional threads are created bypthread_create.
* So we just need to create two thread inmain().
*/
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: %s\n",__func__,strerror(rc2));
rc1 = pthread_create(&tid1,NULL, thread1, &tid2);
if(rc1 != 0)
printf("%s: %s\n",__func__,strerror(rc1));
printf("leave main\n");
exit(0);
}
/*
* thread1() will be execute by thread1, afterpthread_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,(unsignedint)pthread_self());
g_Flag = 1;
printf("this is thread1, g_Flag: %d, thread id is%u\n",g_Flag,(unsignedint)pthread_self());
printf("leave thread1\n");
pthread_exit(0);
}
/*
* thread2() will be execute by thread2, afterpthread_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,(unsignedint)pthread_self());
g_Flag = 2;
printf("this is thread1, g_Flag: %d, thread id is%u\n",g_Flag,(unsignedint)pthread_self());
printf("leave thread2\n");
pthread_exit(0);
}
对程序进行编译时需要加上-lpthread 因为需要使用pthread库的函数
但是通过运行程序 我们可以发现多次运行的结果并不一致 有时候只是执行了线程2,或是只执行了线程1,甚至出现都没有输出g_Flag改变的信息,直接main进程退出。原因在于main函数终止时可能两个线程还没有来的及执行。
当然为了保证thread1,thread2都可以得到执行,可以使用sleep()函数
tips:pthread_exit 与 exit之间的区别
pthread_exit()用于线程退出,可以指定返回值,以便其他线程通过pthread_join()函数获得该线程的返回值。
exit是进程退出,如果在线程函数中调用exit,那么进程中的所有函数都会退出。
5.线程之间的互斥
g_Flag是一个全局变量,两个线程可以同时对它进行操作,需要对它进行加锁保护,thread1和thread2要互斥访问才行。因此需要引入互斥锁的概念。
互斥锁通过一次只有一个线程执行代码的临界段来同步多个线程。互斥锁的相关操作函数如下:
#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t*mptr)
int pthread_mutex_unlock(pthread_mutex_t*mptr)
以上函数返回0表示正常,返回Exxx表示error
在对临界资源进行操作之前,需要pthread_mutex_t先加锁,操作完之后通过pthread_mutex_unlock再解锁。而且在这之前需要声明一个pthread_mutext_t类型的变量,用作前面两个函数的参数。
6.线程之间的同步
对于开始的题目当中第5点,主线程在检测到g_Flag发生变化时退出,就需要用到线程同步技术。线程同步需要条件变量
使用条件变量可以以原子方式阻塞线程,直到某个条件为真为止。条件变量始终与互斥锁一起使用,对条件的测试是在互斥锁(互斥)的保护下进行的。
如果条件为假,线程通常会基于条件变量阻塞,并以原子方式释放等待条件变化的互斥锁。如果另一个线程改变了条件,该线程可能会向相关的条件变量发出信号,从而使一个或者多个等待的线程执行如下操作:(1)唤醒(2)再次获取互斥锁(3)重新评估条件
在以下情况下,条件变量可用于在线程之间同步进程:(1)线程是在可以写入的内存中分配的(2)内存由协作进程共享
条件函数的相关函数如下:
#include<pthread.h>
intpthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr)
intpthread_cond_signal(pthread_cond_t *cptr)
以上函数在正常操作时返回0,错误时返回Exxx正值。
pthread_cond_wait用于等待某个特定的条件为真pthread_cond_signal用于通知阻塞的进程某个特定的条件为真了。在调用这两个函数之前需要声明一个pthread_cond_t类型的变量作为参数。
为什么条件变量始终与互斥锁一起使用,对条件的测试是在互斥锁(互斥)的保护下进行的呢?因为“某个特性条件”通常是在多个线程之间共享的某个变量,互斥锁允许这个变量在不同的线程之中进行检测和设置。
通常pthread_wait只是唤醒等待某个条件变量的一个线程,如果需要唤醒所有等待某个条件变量的线程需要调用:
intpthread_cond_broadcast(pthread_cond_t *cptr)
默认情况下阻塞的线程会一直等待,直到某个条件变量为真,如果想设置最大的阻塞时间可以调用:
intpthread_cond_timewait(pthread_cond_t *cptr,pthread_mutext_t *mptr,const structtimespec *abstime);
如果时间到了,条件变量还没为真,仍然返回,返回错误值ETIME。
7.最终代码
根据以上得到符合要求的最终代码为:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
typedefvoid* (*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*);
/*
* whenprogram is started, a single thread is created, called the initial thread ormain thread.
* Additional threads are created by pthread_create.
* So wejust need to create two thread in main().
*/
int main(intargc, char** argv)
{
printf("enter main\n");
pthread_t tid1, tid2;
intrc1=0, rc2=0;
rc2 = pthread_create(&tid2, NULL,thread2, NULL);
if(rc2!= 0)
printf("%s: %s\n",__func__, strerror(rc2));
rc1 = pthread_create(&tid1, NULL,thread1, &tid2);
if(rc1!= 0)
printf("%s: %s\n",__func__, strerror(rc1));
pthread_cond_wait(&cond,&mutex);
printf("leave main\n");
exit(0);
}
/*
* thread1() will be execute by thread1, afterpthread_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, (unsignedint)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, (unsignedint)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, afterpthread_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, (unsignedint)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, (unsignedint)pthread_self());
pthread_mutex_unlock(&mutex);
printf("leave thread2\n");
pthread_exit(0);
}