学习了几天Linux多线程,大多是看的别人的博客,并试着写了个小例子,现在也把学到的一些东西记录下来,供以后查阅,属于初级入门的范围,望共勉。
进程是系统中程序执行和分配资源的基本单位,每个进程都有自己独立的数据段,代码段和堆栈段。而线程是系统中独立运行的最小单位,可以说操作系统分配CPU时间的最小单位,可以叫轻型的进程。一个进程可以有多个线程,在多进程情况下,每个进程都有着自己的地址空间,消耗资源比较大,而多线程共享进程的地址空间,消耗资源很小。并且在系统调度方面,线程共享地址空间,因此切换速度远远大于进程间的切换速度。并且线程间通信可以直接将共享数据提供给其他线程,通信更加方便省时。
Linux多线程编程时需要包含头文件pthread.h,编译链接的时候需要 libpthread.a,编译的时候要加上-lpthread,以调用静态链接库,因为pthread并非linux系统默认的库。
Linux多线程可以分为两个个部分:一个是线程的控制,另外一个是线程的通信。
线程控制主要是创建线程,线程终止等。
1. 线程的控制
主要有两个方面,一个是线程的创建,另外是线程的退出。
函数 |
功能 |
pthread_create |
创建线程 |
pthread_self |
获取线程本身ID |
pthread_exit |
线程终止 |
1.1线程创建:
函数原型为
Int pthread_create(thread_t *thread,const pthread_attr_t *attr,void *(start_routine)(void*),void *arg);
返回值:若成功返回零,不成功返回错误编号。
返回成功时,由第一个参数thread指向的内存单元被设置为新创建线程的线程ID(此线程ID可以在线程运行过程中用pthread_self获得)。attr参数用于制定各种不同的线程属性。新创建的线程从start_routine函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
参数:
thread:第一个参数为指向线程标识符的指针(可以在线程运行过程中用pthread_self获得)。
attr:第二个参数用来设置线程属性。
start_routine:第三个参数是线程运行函数的起始地址。
arg:最后一个参数是运行函数的参数。
1.2线程退出:
线程退出分为三种情况,一是线程运行结束自动退出,而是线程自己调用pthread_exit主动退出,三是被其他函数调用pthread_cancel终止。
注意进程退出函数是exit,所以线程就用pthread_exit代替exit。
2.线程通信
2.1互斥锁
线程互斥意味着两个线程不能同时进入代码段,Linux可以通过互斥体pthread_mutex_t定义互斥机制完成互斥操作,互斥体机制是在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望锁定一个已经上锁的互斥体,则该线程挂起,进入阻塞状态,直到上锁的线程释放互斥锁为止。
互斥锁的操作主要包括以下几个步骤:
互斥锁初始化:pthread_mutex_init
互斥锁锁定:pthread_mutex_lock
互斥锁尝试锁定:pthread_mutex_trylock
互斥锁解锁:pthread_mutex_unlock
互斥锁销毁:pthread_mutex_destroy
互斥锁主要分为三种:
分为快速互斥锁,递归互斥锁,检索互斥锁:
这三种锁的主要区别在于其他为占有互斥锁的线程在希望得到互斥锁的时候是否需要阻塞等待:
快速互斥锁是指调用线程会阻塞直到拥有互斥锁的线程释放为止;
递归互斥锁能够成功返回并且增加调用线程在互斥上加锁的次数;
检索互斥锁则为快速互斥锁的阻塞版本,他会立即返回并得到一个错误。
Linux系统在缺省参数情况下创建的是快速互斥锁,而一般情况下windows系统是默认采用递归互斥锁的,所以多数有经验的linux开发人员都采用递归互斥锁,以保证和windows的一致性。当然,我们需要根据程序不同的情况定义所需的互斥锁。
函数原型为int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *mutexattr);
Mutexattr:PTHREAD_MUTEX_INITIALIZER快速互斥锁
Mutexattr:PTHREAD_RECRUSIVE_MUTEX_INITIALIZER_NP递归互斥锁
Mutexattr:PTHREAD_REEORCHECK_MUTEX_INITIALIZER_NP检索互斥锁
2.2条件变量condition vararble
与互斥锁不同,条件变量是用来阻塞一个线程,直到某种特定条件发生为止。通常条件变量需要配合互斥锁同时使用。
条件变量使线程可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量通信的一种机制,一个条件变量主要有两种状态:unsignaled(不成立状态)和signaled(成立状态)。而线程间通信主要有两个动作:一个线程等待(条件变量的条件成立),不成立则挂起,另外一个线程使条件成立(给出条件成立的信号)。
条件的检测是在互斥锁保护下进行的,如果一个条件为假(unsignaled),一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另外一个线程改变条件使条件成立,它就会发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。
主要操作函数如下:
pthread_cond_init条件变量初始化。
pthread_cond_wait阻塞等待:条件不成立,则线程将一直处于阻塞状态。
pthread_cond_timedwait超时等待,一定时间后程序自动解除阻塞状态。
pthread_cond_signal在使用这个函数的时候需要注意一个问题,就是linux条件变量自动复位的问题。此函数只将一个处于阻塞的线程解除阻塞状态,即使有多个线程等待此条件发生。
pthread_cond_broadcast将所有等待条件发生的线程解除阻塞状态。
pthread_cond_destroy销毁条件变量。
说了这么多,还是通过一个小程序来增加理解:下面是一个生产者和消费者的例子,生产者不断往一个缓冲区中放数据,消费者不断从缓冲区中取数据。定义了两个条件变量,一个notempty(缓冲区不空),一个是notfull(缓冲区不满)
#include
#include
#define SIZE 4
struct product
{
pthread_mutex_t mutux;
pthread_cond_t notfull;
pthread_cond_t notempty;
int pos;
int buf[SIZE];
};
struct product pdt;
void init(struct product * t)
{
pthread_mutex_init(&t->mutux,NULL);
pthread_cond_init(&t->notfull,NULL);
pthread_cond_init(&t->notempty,NULL);
t->pos=-1;
}
void put(struct product * t,int data)
{
pthread_mutex_lock(&t->mutux);
if(t->pos+1>SIZE-1)//the array is full
{
pthread_cond_wait(&t->notfull,&t->mutux);
}
t->pos=t->pos+1;
t->buf[t->pos]=data;
printf("thread %d put a data %d to pos %d\n",pthread_self(),data,t->pos);
pthread_cond_signal(&t->notempty);
pthread_mutex_unlock(&t->mutux);
}
void get(struct product * t)
{
pthread_mutex_lock(&t->mutux);
if(t->pos<0)//the array is full
{
pthread_cond_wait(&t->notempty,&t->mutux);
}
printf("thread %d get a data %d to pos %d\n",pthread_self(),t->buf[t->pos],t->pos);
t->pos=t->pos-1;
pthread_cond_signal(&t->notfull);
pthread_mutex_unlock(&t->mutux);
}
void * putter(void *)
{
int n;
for(n=0;n<8;n++)
{
put(&pdt,n);
}
}
void * getter(void *)
{
int n;
for(n=0;n<8;n++)
{
get(&pdt);
}
}
int main(void )
{
int err;
pthread_t pt1,pt2,gt1,gt2;
void * retval;
init(&pdt);
err=pthread_create(&pt1,NULL,putter,0);
err=pthread_create(&pt2,NULL,putter,0);
err=pthread_create(>1,NULL,getter,0);
err=pthread_create(>2,NULL,getter,0);
//主线程等待这四个线程结束
pthread_join(pt1,&retval);
pthread_join(pt2,&retval);
pthread_join(gt1,&retval);
pthread_join(gt2,&retval);
return 0;
}
乍一看,程序没什么问题,其实,程序存在一个问题 ,就是当pos会越界。
当然,如果换成只有一个put线程和一个get线程程序就不存在问题了。
发现了当然两个put线程,两个get线程为什么会越界,仔细思考一下,不难得出结论,理解就更加深刻了~
似乎信号量机制可以很好的解决这个问题,待我学习完信号量后,用信号量改写此程序~~