POSIX线程:
一个完成的线程/进程包括三部分,代码+数据+内存栈;子线程和子进程在被创建的时候,
对于fork()创建子进程,三部分都要复制一份,数据包括比如文件描述符,虚拟内存,子进程关闭文件描述符不会影响父进程中的描述符;
对于pthread_create()创建子线程的时候,只有内存栈被复制,其他的部分(代码,数据都是共享的),如果一个线程改变了某变量的值,其他所有的线程都调用的是改变之后的值;
头文件#include <pthread.h>
编译参数: -lpthread
(一)涉及到的类型:
pthread_t, pthread_attr_t, pthread_cond_t, pthread_mutexattr_t, void* (*)(void*),
(二)涉及到的函数:
pthread_cancel,pthread_wait,
pthread_create, pthread_self, pthread_detach, pthread_join, pthread_exit,
pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock, pthread_mutex_destory; //参数都是pthread_mutex_t*
int pthread_attr_init(pthread_attr_t*), int pthread_attr_destory(pthread_attr_t*),
int pthread_attr_setdetachstatus(pthread_attr_t*,int); //设置属性
int pthread_attr_getdetacstatus(const pthread_attr_t*, int*); //获取属性
pthread_cond_wait,pthread_cond_timewait,pthread_cond_signal,pthread_cond_broadcast,pthread_cond_destory;
1.设置线程属性:
一个线程属性对象,(例如pthread_attr_t attr_test),可以创建很多线程,创建线程之后没有必要保持此对象;
在线程的所有属性中,最重要的是分离属性(detach status),一个线程可以是等待线程(joinable thread)or分离线程(detach thread),默认是joinable thread;
对于一个非分离(joinable)线程, 类似于进程中的Zombies进程,joinbale thread在退出后,资源不会被立刻释放,直到被thread_join获取它的返回值;
对于一个分离(detach)线程在退出之后, 资源会被立刻释放, 其他线程无法获悉其返回值;
代码例子,main线程中:
pthread_attr_t attr; //(1)创建变量:
phtread_attr_init(&attr); //(2)初始化此属性变量;
pthread_setdetachstatus(&attr,PTHREAD_CREATED_DETACHED);//(3)
pthread_create(&t,&attr,&function,NULL);//(4)创建线程;
pthread_destory(&attr);//(5)创建完后,即可销毁此属性变量;
pthread_join(t,NULL); //错误!分离的线程无法被join
return; //main线程结束
2.创建线程:
int pthread_create(pthread *thread, pthread_attr_t *attr, void* (*start_routine)(*void), void* arg);
其中pthread_attr_t*和void* arg可设置为NULL;
3.获取线程的pid:
pthread_t pthread_self(void);
4.线程分离:
int pthread_detach(pthread_t thread);
线程设置为分离, 当此线程退出时, 不会向任何其他线程传递返回值, 分离的线程无法被pthread_join();
例子,把线程分离:
void* myThread(void* param)
{
pthread_detach(pthread_self());
pthread_exit(param);
}
5.线程等待(加入):
pthread_join(pthread thread, void **status);
main创建n个线程,无法确定main一定在所有线程都退出后再退出,所以要让main线程等待所有子线程退出后,再退出;
调用者将等待第一参数标识的线程退出后,才能退出; 第二参数保存函数的返回值;
6.线程退出和取消:
int pthread_exit(void* retval);
线程内调用,调用线程退出,参数是线程函数的返回值, 或者可以把参数设为NULL;
线程退出有三种方式,1线程函数返回,2调用pthread_exit,3调用pthread_cancel
一个线程可以请求中止另一个线程,只需调用pthread_cancel(pid)即可;被cancel的线程,可以被pthread_wait(pid),此wait函数会释放线程占用的资源,除非是脱离(detach)线程;
线程是否可以被取消(cancel)是线程的一个属性,类似"分离"属性.线程调用pthread_setcancelstatus()可以实现自身是否可被cancel; 注意区别pthread_attr_setXXX函数组;
pthread_setcancelstatus(PTHREAD_CALCEL_DISABLE,NULL);//调用线程不可被取消!
pthread_setcancelstatus(PTHREAD_CALCEL_ENABLE,NULL);//若第二参数不为NULL,则会存储线程前一个状态的可否取消状态;
在一般应用中,pthread_setcancelstatus可以设置一个代码范围,在此范围内线程不可被取消,比如银行转帐,需要两步操作,A账户扣除,B账户增加,在这过程中不希望线程被取消的;
7.互斥体 mutex:
//初始化
int pthread_mutex_init(pthread_mutex_t* , pthread_mutexattr_t*);//第二个参数NULL是指向互斥锁类型的结构,代表默认属性的互斥锁;
初始化互斥锁属性的代码如下:
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
phtread_mutexattr_init(&attr);
pthread_mutexattr_setkind_np(&attr,PTHREAD_MUTEX_ERRORCHECK_NP);//设置互斥体类型
pthread_mutex_init(&mutex, &attr); //初始化互斥体
pthread_mutex_destory(&attr); //变量pthread_mutexattr_t使用完成后,销毁
//注:带_np结尾的是指"non-portable",不可移植
或者更简单的初始化mutex方法:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //快速互斥
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP: 递归互斥,lock一个已锁互斥体时,不会阻塞,而是记录次数,unlock必须有相同的次数;
PTHREAD_CHECKERROR_MUTEX_INITIALIZER_NP:纠错互斥,
//互斥体锁定
int pthread_mutex_lock(pthread_mutex_t* mutex); //成功返0
如果mutex已被线程A锁定后, 线程B尝试锁定mutex, 此时B线程会被阻塞,直到线程A解除了mutex的锁定, 同一时刻可能会有多个线程阻塞在一个互斥体上;
//互斥体死锁
互斥体的类型分为三种, fast mutex(默认类型), recursive mutex(递归互斥体), error-checking mutex(纠错互斥体),
产生死锁的可能情况: 两个线程,两个互斥体交叉锁定,会出现互相等待的情况,即死锁, 代码例子:
//线程A While(1){ mutex_lock(L1); mutex_lock(L2); do_jobA(); mutex_unlock(L2); mutex_unlock(L1); } //线程B While(1){ mutex_lock(L2); mutex_lock(L1); do_jobB(); mutex_unlock(L1); mutex_unlock(L2); } /* 进程A: mutex_lock(L1); 同时进程B:mutex_lock(L2); 将死锁 */
//非阻塞锁定互斥体:
int pthread_mutex_trylock(pthread_mutex_t* mutex);
/*成功锁定互斥体,返回0; 如果互斥体已被其他的线程锁定,返回EBUSY,不会阻塞*/
代码示例:
int ret = pthread_mutex_trylock(&mutex);
if(ret == EBUSY){
/*互斥体已被锁定,不阻塞*/
}
else if(ret == EINVAL){
assert(0); /*无效属性*/
}
else{
/*解锁成功, 进行关键代码操作*/
ret = pthread_mutex_unlock(&mutex);
}
//互斥体解锁
int pthread_mutex_unlock(pthread_mutex_t* mutex); //成功返0
//互斥体销毁
int pthread_mutex_destory(pthread_mutex_t*); //在程序末尾或其他位置,清理互斥锁,此时互斥体不能是locked状态;
8.线程条件变量
在a情况下让线程继续执行,在b情况下让线程阻塞,当阻塞条件改变时,所有被这个条件变量阻塞的线程都能被激活;
适用于的最简单情况,例如:线程thread1要循环执行从队列queue取出最后一个节点,线程thread2向queue增加节点;或者有更多的读写thread共同操作一个queue;
一般的做法是,所有线程while(1)循环,pthread_mutex_lock和_unlock保护一个代码区域,在此检查queue是否为空,,,为什么这种的效率不高?
(1)
int pthread_cond_init(pthread_cond_t*, NULL);//第二参数为NULL,则创建默认属性
(2)条件变量等待:
int pthread_cond_wait(pthread_cond_t*, pthread_mutex_t*);
设ThreadA,执行pthread_cond_wait函数之前,必须锁定互斥体, pthread_cond_wait以一个原子操作[解锁互斥体]并[开始等待信号], 然后线程相当于wait状态或sleep状态, 处理器会在此时处理其他的线程;
当某线程向ThreadA发送信号时, 线程ThreadA从pthread_cond_wait函数中返回并锁定互斥体;
// cond代码示例(a)
// work_load表示当前负载,当超过MAX_WORK_LOAD时,consumer线程调用process()函数进行work_load减; producer线程每次work_load++后发送一次信号;
pthread_mutex_lock(&mutex);
while(work_load < MAX_WORK_LOAD)
pthread_cond_wait(&cond,&mutex);
process(); //保护部分处理
pthread_mutex_unlock(&mutex);
// cond代码示例(b)
// queue表示一个任务队列构成的链表,consumer线程从链表中取出节点并处理,producer向链表增加节点
/*线程consumer*/
void* consumer(void*){
while(!exit){
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond); //互斥设为非锁定,等待条件信号
/*收到信号,从wait函数返回时,互斥设为锁定,queue--操作 */
pthread_mutex_unlock();
}
printf("recv exit single/n");
return;
}
/*线程producer*/
void* producer(void*){
pthread_mutex_lock(&mutex);
/*queue++*/
pthread_cond_signal(&cond);//或broadcast
pthread_mutex_unlock(&mutex);
return;
}
//注:为了代码健壮性,上面的wait函数应该assert检测返回值
(3)条件变量等待函数的另外一种,指定最长等待时间,超过这个时间,即使没有条件信号到达,也从函数_wait中返回ETIMEOUT:
原型: int pthread_cond_timewait(pthread_cond_t*, pthread_mutex_t*, const struct timespec*);
pthread_mutex_lock(&mutex);
int ret = pthread_cond_timewait(&cond,&mutex,&time);
if(ret == ETIMEOUT) { /*未收到信号,超时*/}
else { /*收到条件信号*/}
(4)条件信号发送函数
int pthread_cond_signal(pthread_cond_t*);
int pthread_cond_broadcast(pthread_cond_t*);
(5)销毁条件变量,在GUN/Linux中,并没有资源真正的被分配给条件变量,destroy只是简单的检查是否还有线程在等待这个条件变量;
int pthread_cond_destory(pthread_cond_t*);
9.线程信号量
涉及类型: sem_t
涉及函数:sem_init, sem_destory, sem_getvalue;
信号量也就是操作系统所应用到的PV原子操作,广泛应用于线程/进程的同步和互斥,信号量本质是一个非负的整数计数器;
/*--PV原子操作的原理: 整数计数器sem, 一次p操作使sem减1,一次v操作使sem加1; 当sem大于等于0时, 线程拥有公共资源访问权,sem小于0时,该线程将阻塞直至sem>=0 */
PV原子操作用于"互斥" or "同步"操作;
(三)阻塞与非阻塞函数,调用函数后,代码是否阻塞在此等待函数返回?
单线程+阻塞函数, 多线程+阻塞函数 ?
pthread_join / pthread_mutex_lock / pthread_mutex_trylock / pthread_cancel / pthread_wait
_mutex_trylock: 如果互斥已经锁定,不会阻断;
_mutex_lock : 如果互斥没有锁定,不阻断;如果互斥已经锁定,则阻塞在此;