Linux支持POSIX多线程接口,称为pthread
(POSIX Thread的简称),编写Linux下的多线程应用程序,头文件需要包pthread.h
一、线程创建
函数名称 | pthread_create |
---|---|
函数功能 | 创建一个线程 |
头文件 | #include<pthread.h> |
函数原型 | int pthread_create( pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); |
参数 | thread:指向线程标识符的指针,用来存储线程标识符 attr:设置线程属性,NULL表示使用默认属性 start_routine:是个函数地址,线程启动后要执行的函数 arg:传给线程启动函数的参数 |
返回值 | 成功返回0,失败返回出错编号 创建成功时,由thread指向的内存单元被设置为新创建的线程的ID |
pthread.h中还声明了一个pthread_self
函数,用于获取当前线程自身的ID
函数名称 | pthread_self |
---|---|
函数功能 | 获取自身线程ID |
头文件 | pthread |
函数原型 | pthread_t pthread_self(void); |
参数 | 无 |
返回值 | >0:返回调用线程的ID |
void*thread_run(void*args)
{
while(1)
{
printf("我是新线程[%s],我创建的线程的ID是:%lu\n",(const char*)args,pthread_self());
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_run,"new thread");
while(1)
{
printf("我是主线程,我创建的线程的ID是:%lu\n",tid);
sleep(1);
}
return 0;
}
结果展示:
我们可以看到主线程和新线程的ID是一样的,而且是很大的值。
我们再来观察一下LWP
,为什么和线程ID
不一样呢?
注意:pthread并不是Linux系统默认的库,而是POSIX线程库。在Linux中将其作为一个库来使用,因此编译链接时需要加上-lpthread
以显式链接该库
如下创建多个线程
//创建多个线程
void *thread_run(void *args)
{
while (1)
{
//printf("我是新线程[%s],我创建的线程的ID是:%lu\n", (const char *)args, pthread_self());
sleep(3);
}
}
int main()
{
pthread_t tid[5];
int i = 0;
for (i = 0; i < 5; i++)
{
pthread_create(tid + i, NULL, thread_run,(void*)"new thread");
}
while (1)
{
printf("我是主线程,我的thread ID是:%lu\n", pthread_self());
printf("##############begin#################\n");
int i = 0;
for (i = 0; i < 5; i++)
{
printf("我创建的线程[%d]是:%lu\n", i, tid[i]);
}
printf("##############end###################\n");
sleep(1);
}
return 0;
}
结果展示:
线程的健壮性不强
//创建多个线程,其中让3号线程出现野指针
void *thread_run(void *args)
{
int num=*(int*)args;
while (1)
{
printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
sleep(10);
// 野指针问题
if(num==3)
{
printf("thread number:%d quit\n",num);
int *p=NULL;
*p=100;
}
}
}
int main()
{
pthread_t tid[5];
int i = 0;
for (i = 0; i < 5; i++)
{
pthread_create(tid + i, NULL, thread_run,(void*)&i);
sleep(1);
}
while (1)
{
printf("我是主线程,我的thread ID是:%lu\n", pthread_self());
printf("##############begin#################\n");
int i = 0;
for (i = 0; i < 5; i++)
{
printf("我创建的线程[%d]是:%lu\n", i, tid[i]);
}
printf("##############end###################\n");
sleep(1);
}
return 0;
}
结果展示:
二、线程等待
一般而言,线程也是需要被等待的,如果不等待,可能会导致类似于”僵尸进程”的问题!
函数名称 | pthread_join |
---|---|
函数功能 | 等待线程结束 |
头文件 | #include<pthread.h> |
函数原型 | int pthread_join(pthread_t thread, void **retval); |
参数 | thread:线程ID retval:它指向一个指针,用来存储被等待线程的返回值, 意思就是,这是一个输出型参数,用来获取新线程退出时候,函数的返回值 |
返回值 | 成功返回0 失败返回错误码 |
// 线程等待
#define NUM 1
void *thread_run(void *args)
{
int num=*(int*)args;
while (1)
{
printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
sleep(10);
break;
}
return (void*)111;
}
int main()
{
pthread_t tid[NUM];
int i = 0;
for (i = 0; i < NUM; i++)
{
pthread_create(tid + i, NULL, thread_run,(void*)&i);
sleep(1);
}
//指针变量,本身就可以充当某种容器保存数据
void* status=NULL;
pthread_join(tid[0],&status);
printf("ret=%d\n",(int)status);
return 0;
}
结果展示:
我们的确实拿到线程退出的返回值。这里的返回值是int,也可以是其他类型的变量,也可以是对象的地址(不能是临时的)。
三、线程终止
线程终止的方案
- 函数中
return
(1、main函数中return的时候代表主线程或者进程退出 2、其他线程函数return代表当前线程退出) - 新线程通过
pthread_ exit
终止自己( 而exit是终止进程,不要在其他线程中调用,如果你就想终止一个线程的话! ! ) - 调用
pthread_cancel
取消一个线程
函数名称 | pthread_exit |
---|---|
函数功能 | 结束调用线程 |
头文件 | #include<pthread.h> |
函数原型 | void pthread_exit(void *retval); |
参数 | 指向退出信息的指针 |
返回值 | 无 |
// 线程终止
#define NUM 1
void *thread_run(void *args)
{
int num=*(int*)args;
while (1)
{
printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
sleep(2);
break;
}
pthread_exit((void*)123);
}
int main()
{
pthread_t tid[NUM];
int i = 0;
for (i = 0; i < NUM; i++)
{
pthread_create(tid + i, NULL, thread_run,(void*)&i);
sleep(1);
}
//指针变量,本身就可以充当某种容器保存数据
void* status=NULL;
// 也要循环式等待线程
for(i=0;i<NUM;i++)
{
pthread_join(tid[0],&status);
}
// 这里是int,也可以是其他类型的变量或者对象的地址(不能是临时的)
printf("ret=%d\n",(int)status);
sleep(3);
return 0;
}
结果展示:
我们刚开始主线程+新创建的两个线程同时运行,新线程提前终止,主线程后终止退出,我们能看到明显的现象。
我们也是可以直接调用exit
函数
// 也可以直接调用exit函数
exit(123);
//pthread_exit((void*)123);
函数名称 | pthread_cancel |
---|---|
函数功能 | 取消一个线程 |
头文件 | #include<pthread.h> |
函数原型 | int pthread_cancel(pthread_t thread); |
参数 | thread:要取消的线程ID |
返回值 | 成功返回0 失败返回错误码 |
// pthread_cancel取消线程
#define NUM 1
void *thread_run(void *args)
{
int num=*(int*)args;
while (1)
{
printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
sleep(2);
}
}
int main()
{
pthread_t tid[NUM];
int i = 0;
for (i = 0; i < NUM; i++)
{
pthread_create(tid + i, NULL, thread_run,(void*)&i);
sleep(1);
}
printf("wait sub thread...\n");
sleep(5);
printf("cancel sub thread..\n");
pthread_cancel(tid[0]);
void* status=NULL;
for(i=0;i<NUM;i++)
{
pthread_join(tid[0],&status);
}
printf("ret=%d\n",(int)status);
sleep(3);
return 0;
}
结果展示:
其中的退出结果ret=-1代表的是什么呢?
如果线程被其他线程调用pthread_cancel取消掉,value_ptr所指向的单元里面存放的常数是PTHREAD_CANCELED
就是-1
在新线程中取消主线程(实际中不推荐这样写)
// 新线程取消主线程线程
#define NUM 1
pthread_t g_id;// 主线程ID定为全局的
void *thread_run(void *args)
{
int num=*(int*)args;
while (1)
{
printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
sleep(2);
pthread_cancel(g_id);// 在新线程中取消主线程
}
}
int main()
{
g_id=pthread_self();// 拿到主线程的线程ID
pthread_t tid[NUM];
int i = 0;
for (i = 0; i < NUM; i++)
{
pthread_create(tid + i, NULL, thread_run,(void*)&i);
sleep(1);
}
printf("wait sub thread...\n");
sleep(50);// 50秒内主线程创建新线程,并且不退出
printf("cancel sub thread..\n");
pthread_cancel(tid[0]);
void* status=NULL;
for(i=0;i<NUM;i++)
{
pthread_join(tid[0],&status);
}
printf("ret=%d\n",(int)status);
sleep(3);
return 0;
}
结果展示:
确实主线程被取消了,但是进程却没有退出,会出现类似于“僵尸进程”的现象
四、线程分离
上述列举的是几种等待的情况,但是我们不想等待呢?
- 在任何一个时间点上,线程是可结合的(
joinable
),或者是分离的(detached
)。一个可结合的线程能够被其他线程回收其资源和杀死;在被其他线程回收资源之前,它的存储器资源是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。 - 线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,在这种情况下,原有的线程等待创建的线程结束。只有当
pthread_ join()
函数返回时,创建的线程才算终止,才能释放已占用的系统资源。而分离线程如果没有被其他的线程所等待,当该线程运行结束,线程就终止了,同时也会立刻释放系统资源。线程分离使用pthread_ detach()
实现的。
(可以类比之前信号中所学的signal
(SIGCHLD,SIG_IGN))。
函数名称 | pthread_detach |
---|---|
函数功能 | 使线程处于分离状态 |
头文件 | #include<pthread.h> |
函数原型 | int pthread_detach(pthread_t threadID) |
参数 | threadID:要分离的线程 |
返回值 | 0:成功 错误号:失败 |
// 线程分离
#define NUM 1
void *thread_run(void *args)
{
pthread_detach(pthread_self());
int num=*(int*)args;
while (1)
{
printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
sleep(2);
break;
}
return (void*)(111);
}
int main()
{
pthread_t tid[NUM];
int i = 0;
for (i = 0; i < NUM; i++)
{
pthread_create(tid + i, NULL, thread_run,(void*)&i);
sleep(1);
}
printf("wait sub thread...\n");
sleep(1);
printf("cancel sub thread..\n");
void* status=NULL;
int ret=0;
for(i=0;i<NUM;i++)
{
ret=pthread_join(tid[0],&status);
}
printf("ret:%d status:%d\n",ret,(int)status);
sleep(3);
return 0;
}
结果展示:
拿到了ret和status
五、线程ID以及进程地址空间布局
pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID, 属于NPTL(NATIVE POSIX Thread Library)线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID。
void* thread_run(void*args)
{
while(1)
{
printf("%s id:0x%x\n",(const char*)args,pthread_self());
sleep(2);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_run,(void*)"new thread");
while(1)
{
printf("main thread id:0x%x\n",pthread_self());
sleep(1);
}
return 0;
}
我们查看到的线程id是pthread库的线程id,不是Linux内核中的LWP,pthread库的线程id是一个内存地址!(虚拟地址)
每个线程都要又运行时的临时数据,每个线程都要又自己的私有栈结构!
所以使用了pthread的数据结构:pthread_t,这个结构用来标识线程的ID。
我们如何快速的找到每个特定的线程呢?
我们只需要拿到每个pthread_t id(虚拟地址),那么我们就能在快速的找到这个线程的所有的信息。
用户级线程和内核级线程1:1对应