一、线程的基础
二、线程的创建、退出、分离、汇合
三、线程对共享资源的访问
四、线程的同步
三、线程对共享(临界)资源的访问
线程创建完毕,进程中的所有线程是异步的。所以线程函数尽量写成可重入的,可以保证线程函数的安全。即尽量将函数所用到的资源控制在栈或堆中。
例:线程函数不是可重入函数
#include<stdio.h> #inclede<pthread.h> int counter; void *doit(void *arg){ int i,value; for(i=0;i<5000;i++){ value=counter; printf("%lu:%d ",pthread_self(),value+1); counter=value+1; } return NULL; } int main(void){ pthread_t tidA,tidB; //创建两个线程 pthread_create(&tidA,NULL,doit,NULL) ; pthread_create(&tidA,NULL,doit,NULL) ; //等待线程的汇合 pthread_join(tidA,NULL); pthread_join(tidB,NULL); return 0; }
四、线程的同步
多线程对临界资源访问的时候,需要由异步变为同步。posix线程提供了三种同步方式:mutex锁、条件变量、信号量
在线程实际运行过程中,我们经常需要多个线程保持同步。这时可以用互斥锁来完成任务;互斥锁的使用过程中,主要有pthread_mutex_init,pthread_mutex_destory,pthread_mutex_lock,pthread_mutex_unlock这几个函数以完成锁的初始化,锁的销毁,上锁和释放锁操作。
1、mutex锁
(1),锁的创建
锁可以被动态或静态创建,可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
另外锁可以用pthread_mutex_init函数动态的创建、也可对静态mutex锁进行初始化,函数原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * mutexattr)
功能:
使用mutexattr属性初始化指定的mutex锁。
参数:
mutex:要创建的并初始化的mutex锁
mutexattr:初始化属性,NULL的话,则使用默认属性。
返回值:
总是返回0
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁一个指定的mutex锁,并释放它所占用的资源。但是这个mutex锁必须处于解锁状态,如果没有资源和该mutex锁联系,那么函数什么都不用做
参数:
mutex:指定要销毁的mutex锁
返回值:
成功:0
错误:非0错误码
(2),锁的属性
互斥锁属性可以由pthread_mutexattr_init(pthread_mutexattr_t *mattr);来初始化,然后可以调用其他的属性设置方法来设置其属性;
互斥锁的范围:可以指定是该进程与其他进程的同步还是同一进程内不同的线程之间的同步。可以设置为PTHREAD_PROCESS_SHARE和PTHREAD_PROCESS_PRIVATE。默认是后者,表示进程内使用锁。可以使用int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared)
pthread_mutexattr_getshared(pthread_mutexattr_t *mattr,int *pshared)
用来设置与获取锁的范围;
互斥锁的类型:有以下几个取值空间:
PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
可以用
pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type)
pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type)
获取或设置锁的类型。
(3),锁的释放
调用pthread_mutex_destory之后,可以释放锁占用的资源,但这有一个前提上锁当前是没有被锁的状态。
(4),锁操作
对锁的操作主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个。
int pthread_mutex_lock(pthread_mutex_t *mutex),这个函数为阻塞函数。
int pthread_mutex_unlock(pthread_mutex_t *mutex),解除一个给定的mutex锁,这个mutex锁被假定为上锁状态。
int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待
例:
#include<stdio.h>
#inclede<pthread.h>
int counter;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *doit(void *arg){
int i,value;
for(i=0;i<5000;i++){
pthread_mutex_lock(&mutex);
value=counter;
printf("%lu:%d
",pthread_self(),value+1);
counter=value+1;
pthread_mutex_unlock(&mutex)
}
return NULL;
}
int main(void){
pthread_t tidA,tidB;
//创建两个线程
pthread_create(&tidA,NULL,doit,NULL) ;
pthread_create(&tidA,NULL,doit,NULL) ;
//等待线程的汇合
pthread_join(tidA,NULL);
pthread_join(tidB,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
2、条件变量
假如线程A需要等待某个条件成立才能继续执行,如果这个条件现在不成立,则线程A就阻塞等待,而线程B在执行过程中,使这个条件成立。然后唤醒线程A继续往下执行,这个条件称为条件变量。
pthread库中使用pthread_cond_t的类型来表示条件变量的类型。主要有以下操作:pthread_cond_t cond =PTHREAD_COND_INITIALIZER(静态初始化一个条件变量);pthread_cond_init(3)、pthread_cond_signal(3)、pthread_cond_broadcast(3)
pthread_cond_init(3)
头文件:同上
函数原型:int pthread_cond_init(pthread_cond_t *cond,pthread_condattr* cond_attr);
功能:
使用cond_attr属性初始化cond条件变量,如果缺省为NULL,那么就使用默认属性
参数:
cond:要初始化的条件
cond_attr:要初始化条件的属性
返回值:
成功:0
错误:非0错误码
int pthread_cond_signal(pthread_cond_t *cond);
功能:
重启等待在cond条件下的线程之一(如果有几个,不确定是哪一个),如果没有线程等待,那么什么操作都不做
参数:
cond:要重启线程的等待条件
返回值:
成功:0
错误:非0错误码
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
重启等待在cond条件下的所有线程,如果没有线程等待,那么什么操作都不做
参数:
cond:要重启线程的等待条件
返回值:
成功:0
错误:非0错误码
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t* mutex);
功能:
调用时首先解锁mutex锁(相当于执行pthread_unlock_mutex操作),并且等待cond进入signaled(这个线程不消耗任何CPU时间,知道条件变量进入signaled),当前线程进入此函数时,这个mutex必须处于上锁状态。总结来说,先解锁mutex;等待条件成立;重新加锁
参数:
cond:
线程等待的条件
mutex:
指定使用的mutex锁
int pthread_cond_timedwait(pthread_cond_t*cond,pthread_mutex_t *mutex,const struct timespec *abstime);
功能;
与pthread_cond_wait(3)操作类似,但是多了一个等待时间,再时间超时时,mutex会重新上锁,并且函数返回一个错误码ETIMEOUT。
参数:
cond:同上
mutex:同上
abstime:等待的绝对时间
返回值:
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
成功:0
错误:非0错误码
释放条件变量所占用的资源,且没有为这个条件变量等待的线程。如果没有资源和条件变量关联,那么函数不做任何操作。
参数:
cond:要初始化的条件
cond_attr:要初始化条件的属性
返回值:
成功:0
错误:非0错误码
例:生产者和消费者问题
假设生产者只生产出来的节点串到链表的头部,消费者从链表的头部取走一个节点。如果链表中没有节点的时候,消费者一直等待生产者生产。
大概思路:生产者生产一个新节点;之后初始化该节点,使用一个变量指向该节点,将new指向的节点添加到链表的头部。
链表不为空,消费者从头部取下节点;如果链表为空,那么等待生产者生产节点,且如果生产者生产好了,需要通知消费者。(代码见笔记本)
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> //产品节点 typedef struct node{ int data; struct node *next; }node_t; typedef node_t *node_p; pthread_cond_t cond=PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; node_p head;//链表头部 //生产者函数 void *prodoctor(void *arg){ while(1){ node_p new; //创建新节点 new=(node_p)malloc(sizeof(node_t)); //初始化新节点 new->next=NULL; new->data=rand()%1000+1; //加锁 pthread_mutex_lock(&mutex); //新节点添加到链表的头部 new->next=head; head=new; printf("生产了一个节点!%p:%d ",new,new->data); //解锁 pthread_mutex_unlock(&mutex); //告诉消费者有节点 pthread_cond_signal(&cond); sleep(rand()%5); } } //消费者函数 void *comsumer(void *arg){ node_p tmp=NULL; while(1){ pthread_mutex_lock(&mutex); while(head==NULL){ pthread_cond_wait(&cond,&mutex); } //链表不为空 tmp=head; head=head->next; pthread_mutex_unlock(&mutex); printf("消费头节点%p:%d ",tmp,tmp->data); free(tmp); tmp=NULL; } } int main(void){ srand(time(NULL)); pthread_t pid,cid; //创建两个线程,一个为生产者,另一个为消费者。 pthread_create(&pid,NULL,prodoctor,NULL); pthread_create(&cid,NULL,comsumer,NULL); //等待线程汇合 pthread_join(pid,NULL); pthread_join(cid,NULL); //销毁锁与条件变量 pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; }