linux环境下的线程有joinable
和unjoinable
两种状态,joinable
下的线程在完成线程函数(调用pthread_exit()
或者return
函数后)之后不会自动释放资源,需要在主线程中调用pthread_join()
函数进行阻塞回收,而unjoinable
状态的线程在完成子线程函数之后可以自动释放资源(不需要主线程擦屁股)。创建线程时可以通过指定参数来定义线程的状态(默认为joinable
),也可以通过pthread_detach(pthid)
函数来将子线程的状态转换为``unjoinable,由于在主线程中调用
pthread_join()会使得主线程阻塞(等待当前子线程的返回),所以一般情况下会把线程的状态调为
unjoinable`,从最大限度的增大主线程的性能。
在linux下创建和管理线程的方法声明在pthread.h
文件中,下面对几个基本函数进行解析:
pthread_create
该函数的作用是创建一个进程,其函数原型为:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
参数解析:
pthread_t *thread
:线程id的地址,线程id由pthread_t
来定义,如pthread_t pthid
。const pthread_attr_t *attr
:该参数指定线程的状态,比如可以指定detach
状态,如果为NULL
则为选择默认的joinable
。void *(*start_routine) (void *)
:函数指针,指向子线程的回调函数,需要注意的是该函数返回的是一个空指针,传入的也是一个空指针。void *arg
:向回调函数传入的参数,注意类型是空指针(传入地址),如果你传入的参数是一个非空指针,需要在回调函数中将空指针强制转换为你想要的类型,因为在传入参数的时候,其他类型的指针会退化为空指针。比如你传入了一个整型变量a
的指针,需要在回调函数中有以下代码int* a = (int*)arg
,才可以在回调函数中使用整型变量a
.这里的参数尽量传入一个自定义指针,原因在最后一节详解。
pthread_detach
该函数的功能是将子线程detach(分离),将线程的状态改为unjoinable
,其函数声明为int pthread_detach(pthread_t thread)
,传入的参数是线程id。
pthread_join
该函数一般在主线程中调用,用来将主线程阻塞,以等待当前的子线程执行完毕(主线程速度比子线程快,如果不等待可能主线程结束了,子线程还没执行完就因此关闭了),其函数原型为:
int pthread_join(pthread_t thread, void **retval)
参数解析:
pthread_t thread
:等待的线程idvoid **retval
:存储子线程的返回值,(子线程返回值一般由pthread_exit()
返回),如果填入NULL
表示不保存返回值。这里需要注意的还是返回值的类型是空指针的指针,如果需要使用该返回值,也需要做强制类型转换。
注:我们一般在编写多线程的程序比如多线程的服务器时,一般不会使用pthread_join
这个函数,原因有下:1,调用该函数之后,主线程会在该函数的调用处进行阻塞,等待子线程执行完毕才能继续向下执行,这样就会大大拖慢主线程的效率,2,由于主线程使用循环创建多线程,如果使用了该函数,主线程只有再一个线程执行完成之后才会再去创建一个线程,也就是每一个线程是等前一个线程执行完之后在创建再去执行,这样的多线程显然效率不高。 一般情况之下,我们会调用pthread_detach
函数,使得子线程分离,状态变为unjoinable
,这样每一个线程执行完之后自动释放内存,主线程也就不需要调用pthread_join
函数进行阻塞,而是可以去循环创建多个子线程,提高程序效率。
pthread_create扩展
我们在写一个多线程程序时比如多线程的服务器,会使用循环创建多线程,而且我们知道主线程的运行速度一般快于子线程,所以在创建线程时就存在一个问题,那就是我们应该如何传参,如果传入一个变量的地址,有可能子线程在没有利用该变量执行完程序时变量的值就被主线程修改了,所以我们应该传入一个自定义指针,这样相当于为每一个子线程的参数单独创建了一块地址,不会被主线程修改,之前的情况相当于多个子线程的参数共享一块地址,下面看例子:
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string.h>
using namespace std;
void* print(void* arg){
sleep(1);
int* i = (int *)arg;
std::cout <<i<<" "<< *i<< std::endl; //打印变量地址和值
}
int main(int argc, const char** argv) {
int i = 0;
while (i<30)
{
int a = i; //是使变量a等于i
pthread_t pthid;
pthread_create(&pthid,NULL,print,&a);//创建线程
pthread_detach(pthid); //改变线程状态
++i;
}
sleep(3);
return 0;
}
上述程序中主线程循环创建子线程并将循环累加的变量i
的值赋给子线程进行打印结果过为:
我们发现,所有打印的所有变量的值均为30(16进制,我也不知咋回事),这是因为在子线程中我们先睡眠了1秒钟,这期间主线程的循环早已经完成,将i
的值累加到了30,看上面的输出结果中所有的地址都是一样的,都是主线程中定义的a
的地址。
如果我们每一次循环都定义一个指针a
,情况会怎么样呢,先看代码:
void* print(void* arg){
sleep(1);
int* i = (int *)arg;
std::cout <<i<<" "<< *i<< std::endl; //打印变量地址和值
}
int main(int argc, const char** argv) {
int i = 0;
while (i<30)
{
int* a = new int(i); //自定义指针
pthread_t pthid;
pthread_create(&pthid,NULL,print,a);//创建线程
pthread_detach(pthid); //改变线程状态
++i;
}
sleep(3);
return 0;
}
注:一定在堆上分配空间(即使用new分配空间,返回一个指针),千万不要使用int *a = i
这种在栈上分配空间的方法,因为int *a = i
指明a
就是定义一个指向某一变量的指针,在每次循环中它指向的地址不会发生改变(与前面的实验结果一致),使用int* a = new int(i)
每一次循环都会开辟一个整型空间并返回一个指针,所以每次循环中a
指向的空间不一样的,看实验结果:
这个图没有截完整,其实1-30每一个数字都有的,不过影响不大,因为我们可以很清晰的看出每一个参数的地址都不一样,故而输出的值都各不相同(1-30)。