线程函数
在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。该系统copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。不过这个copy过程和fork不一样。 copy后的进程和原先的进程共享了所有的变量,运行环境。这样,原先进程中的变量变动在copy后的进程中便能体现出来。 每一个线程都会有自己独立的栈。
Linux下的多线程编程接口遵循POSIX标准,称为pthread
编译链接需要添加的参数: -lpthread
因为pthread并非Linux系统的默认库,而是POSIX线程库。在Linux中将其作为一个库来使用,因此加上 -lpthread(或-pthread)以显式链接该库。
线程创建与回收
3.7.3.3、线程函数退出相关
(1)pthread_exit与return退出(最正规的做法)
一个线程的结束有两种途径,一种是函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。
(2)pthread_cleanup_push(压栈)
(3)pthread_cleanup_pop(弹栈)
注意在线程里不能用exit函数来退出,因为在线程里一旦用这种方式退出则整个程序就退出了。
(exit会退出整个进程)
1、pthread_create:主线程用来创造子线程的
#include<pthread.h>
int pthread_create(
pthread_t *restrict tidp, //线程ID指针 指向线程标识符的指针。
const pthread_attr_t *restrict_attr, //设置线程属性 若不设定可以用NULL
void*(*start_rtn)(void*), //确定线程运行函数的起始地址
void *restrict arg //运行函数的参数
);
返回值:成功则返回0,出错则返回出错编号信息。(不会修改error,无法用perror打印错误信息)
线程ID:换成%lu打印出来是正常的(否则会打印出负数)
typedef unsigned long int pthread_t;
2、pthread_join:等待(阻塞)回收同进程下的另一线程
int pthread_join(pthread_t thread, void **retval);
args:
pthread_t thread: 被连接线程的线程号
void **retval : 不为NULL时,那么线程thread的返回值存储在该指针指向的位置。该返回值可以是由pthread_exit给出的值,或者该线程被取消而返回PTHREAD_CANCELED(-1)
return:
线程连接的状态,0是成功,非0是失败
当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。这里有三点需要注意:
①被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,比如 malloc() 分配的空间。
②一个线程只能被一个线程所连接(等待)。
③被连接的线程必须是非分离的,否则连接会出错。
pthread_join()有两种作用:
①用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。
②对线程的资源进行回收:【类似于多进程中的wait函数】如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。
由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。
一个线程的3种退出方式:
(1)线程只是从启动例程中返回,返回值是线程的退出码。
(2)线程可以被同一进程中的其他线程取消pthread_cancel
(3)线程调用pthread_exit
3、pthread_exit:线程显式调用pthread_exit退出(也可隐式退出)
用pthread_exit()用来退出线程并获得线程返回值,但是退出线程所占用的资源不会随着线程的终止而得到释放
void pthread_exit( void * value_ptr );
pthread_exit函数唯一的参数value_ptr是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给value_ptr。
4、pthread_cancel:线程取消函数,用来取消同一进程中的其他线程(得设置成可被取消)
杀死线程也不是立刻就能完成,必须要到达取消点。
可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用pthread_testcancel()函数自行设置一个取消点。
【一个线程可以隐式的退出,也可以显式的调用pthread_exit函数来退出。】
void pthread_exitvoid *retval);
//(一般是被调用的子线程)显式地调用pthread_exit函数来使线程自己退出(类似于进程调用exit)
argv:
void *retval是函数的返回代码,只要pthread_join中的第二个参数void **retval不是NULL,这个值将被传递给void **retval。
说明:retval:pthread_exit()调用线程的返回值,可由其他函数如pthread_join来检索获取(前提是第二个参数不为NULL)。
int pthread_cancel(pthread_t thread);
argv:
thread是要取消线程的标识符ID
(要求)取消某一个线程,
但是被要求(收到取消请求)的线程可以决定是否允许被取消以及如何取消,这分别由如下两个函数完成。
5、被要求取消的线程设置能否被pthread_cancel取消
int pthread_setcancelstate(int state, int *oldstate);
argv:
int state 设置线程的取消状态(是否允许取消)
int *oldstate 记录线程原来的取消状态
state可以设置为:
PTHREAD_CANCEL_CNABLE,允许线程被取消。它是线程被创建时的默认取消状态。
PTHREAD_CANCEL_DISABLE,禁止线程被取消。这种情况下,如果一个线程收到取消请求,则它会将请求挂起,直到该线程允许被取消
int pthread_setcanceltype(int type, int *oldtype);
argv:
int type 设置线程的取消类型(如何取消)
int *oldtype 记录线程原来的取消类型
type参数也有两个可选值:
PTHREAD_CANCEL_ASYNCHRONOUS: 线程随时都可以被取消。它将使得接收到取消请求的目标线程立即采取行动。
PTHREAD_CANCEL_DEFERRED: 允许目标线程推迟行动,直到它调用了下面几个所谓的取消点函数中的一个:pthread_join、
pthread_testcancel、pthread_cond_wait、pthread_cond_timedwait、sem_wait和
sigwait。根据POSIX标准,其他可能阻塞的系统调用,比如read、wait,也可以成为取消点
不过为了安全起见,最好在可能会被取消的代码中调用pthread_testcancel函数设置取消点
这两个函数的第二个参数均设为NULL???不需要吗?
在被取消的线程中,这两个函数一般结合起来使用
5、pthread_self:获取当前线程id
pthread_t pthread_self(void);
6、pthread_detach :主线程用来分离子线程,分离后主线程不必再去回收子线程,而是让子线程自己去管理&回收自己,
int pthread_detach(pthread_t thread);
形参:thread是要释放线程的标识符ID。
返回值:若是成功返回0,否则返回错误的编号。
例如:pthread_detach(pthread_self()) 子线程自己设置为自己结束时自己回收资源
linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态。
1、joinable(线程默认状态)
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时,都不会释放线程所占用堆栈和线程描述符(总计8K多),只有当调用了pthread_join之后这些资源才会被释放。
2、unjoinable
该状态下,这些资源在线程函数退出时或pthread_exit时自动会被释放。
unjoinable属性设置:
①在pthread_create时指定
②在线程创建后在线程中pthread_detach自己设置,如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。
7、pthread_equal:比较两个线程是否为同一线程
int pthread_equal (pthread_t thread1, pthread_t thread2);
返回值:不相等返回0,相等非零。
8、线程清理函数的注册(可多个)和调用
pthread_cleanup_pop函数和pthread_cleanup_push函数是一种避免死锁的一种安全机制。
1、void pthread_cleanup_push(void (*rtn)(void *), void *arg); 注册清理函数(类似压栈,可注册多个清理函数,先注册后调用)
argv:
void (*rtn)(void *) 线程清理函数
void *arg 清理函数传参
2、void pthread_clean_pop(int execute);
argv:
int execute=0时仅在线程调用pthread_exit或者其它线程对本线程调用pthread_cancel时,在弹出“清理函数”同时执行该“清理函数”
=非零时会在弹出“清理函数”的同时执行该“清理函数”
当线程执行以下动作时,调用清理函数,调用的参数为arg,清理函数rtn的调用顺序是由pthread_cleanup_push函数来安排的。
●调用pthread_exit时
●响应取消请求时
●用非零execute参数调用pthread_cleanup_pop时
以及其他线程函数,如线程属性相关函数
线程锁(多线程中的锁机制)
由于多线程之间是并发执行的,而系统调度又是随机的,因此在写多线程程序时会出现很多问题,这时就免不了要用到各种锁机制来保证线程安全且按我们的意愿正确执行。
互斥锁
1、定义一个互斥量
pthread_mutex_t mutex;
2、初始化互斥量
1、静态分配 pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZED
2、动态分配
int pthread_mutex_init( pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
3、上锁
pthread_mutex_lock(&mutex);
4、解锁
pthread_mutex_unlock(&mutex);
5、销毁
pthread_mutex_destroy(&mutex);
线程锁:(上厕所的例子)
int lock=0;
if(lock==0) //判断是否处于解锁状态
{
(1)lock++; 上锁
(2)pthread_cleanup_push 注册线程清理函数
pthread_cleanup_push(function,arg);
pthread_cleanup_push(function1,arg);
(3)//需要执行的东西
//子线程有可能在这个步骤中被主线程取消cancle(子线程死在厕所里,别人也进不去了)
//解决方案就是pthread_cleanup_push(压栈)函数
(4)pthread_cleanup_pop(0); 见前面函数简析
pthread_cleanup_pop(0); 总结就是pthread_cleanup_pop函数和pthread_cleanup_push函数是一种避免死锁的一种安全机制。
(5)lock--; 解锁
}
void function(void * arg) 线程清理函数
{
lock--; //表示解锁
}
设置线程的分离状态属性:
线程的分离状态也就是说让一个线程不用被主线程等待回收,而是自己去回收自己,自己终止自己,并释放系统资源。
设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)函数, 第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)----------------这个函数写在主线程中
设置线程的优先级属性:线程的优先级用来指定线程的执行规则,使得重要的线程有机会被优先执行,它存放在结构体sched_param中;线程的优先级使用pthread_attr_getschedparam()函数和pthread_attr_setschedparam()设置。我们一般是(1)先读取优先级,(2)对取得的值进行修改(3)再存放回去。----------------这个函数写在主线程中
(6)线程同步:目前实现线程同步的主要方式就是:互斥量、条件变量、信号量
线程终止的3种方式:
总结:终止某个线程而不终止整个进程,有三种方法:
1、从线程主函数return。这种方法对主控线程不适用,从main函数return相当于调用exit。
2、一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
3、线程可以调用pthread_exit终止自己。
情况一、一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。
情况二、线程被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
情况三、