1.如何理解线程
定义:在一个程序里的一个执行路线就叫做线程(thread)。
更准确的定义是:线程是“一个进程内部的控制序列”。
每个进程都有自己的进程地址空间和task_struct结构体,如果我们通过一定的方式在创建进程的时候,将当前进程的资源(进程地址空间),划分给不同的task_struct。
线程在进程的地址空间内运行,是OS调度的基本单位。
- 在Linux下,PCB<=其他操作系统的PCB(进程里面只有一个执行流的时候相等)。所以Linux下的进程,统一称之为轻量级进程。
- Linux没有真正意义上的线程相关的数据结构,Linux是用进程pcb模拟线程的。Linux只能提供轻量级进程的接口。
- Linux在用户层实现了一套用户层多线程方案,以库的方式——pthread线程库
1.2 进程和线程的初步对比
- 进程是资源分配的基本单位。
- 线程是调度的基本单位。
- 线程共享进程数据,文件描述符、每种信号的处理方式、当前工作目录、用户id和组id
- 线程拥有自己的一部分数据:线程ID、一组寄存器、栈、errno、信号屏蔽字、调度优先级。
- 线程切换成本低,因为地址空间不需要切换、页表不需要切换、CPU内部的缓存(L1~L3cache)不用重新读取数据。进程切换会导致cache立即失效,只能重新缓存。
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- 一个线程出问题,那整个进程都会终止。
- 线程在创建并执行时,线程也是需要进行等待的。不然会出现内存泄漏问题。
1.3 线程的缺点
- 性能损失。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失。增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低。因共享了不该共享的变量而造成不良影响,线程之间是缺乏保护的。
- 缺乏访问控制。
- 编程难度提高。
2. 线程相关函数
2.1 创建线程
pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread,const pthread_arr_t * arrt,
void*(*start_routine)(void*),void*arg)
参数介绍
thread 线程的id
arrt 线程的属性 先默认nullptr
函数指针 是线程需要去执行的代码的入口函数
arg 传递给函数指针的参数
成功返回0
注意
编译时一定要加 -lpthread g++ -o $@ $^ -std=c++11 -lpthread
由于回调函数的参数是void*类型 所以想传什么类型都可以,也可以传一个结构体以携带更多的信息给arg。
代码测试及结果
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* threadRun(void* args)
{
const string name=(char*)args;
while(true)
{
cout<<name<<" -> pid: "<<getpid()<<"\n"<<endl;
sleep(1);
}
return (void*)10;
//不需要返回其他数据时 return nullptr;
}
int main()
{
pthread_t tid[5];//pthread_t 其实本质就是一个 无符号的长整型
char name[64];
for(int i=0;i<5;i++)
{
snprintf(name,sizeof(name),"%s-%d","thread",i);//写到哪里 写多少 写什么
pthread_create(tid+i,nullptr,threadRun,(void*)name);
sleep(1);//传参有bug 等待一下
}
while(true)
{
cout<<"main thread pid"<<getpid()<<endl;
sleep(3);
}
return 0;
}
所有线程的pid是相同的,那我们查看轻量级进程
ps -aL | grep mythread | head -1 && ps -aL
LWP : 轻量级进程对应的PID ,PID和LWP一样的是主进程。
2.2 等待线程
pthread_join
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数介绍
thread 等待线程的tid。
retval 获取回调函数的返回值。
成功返回0,错误为错误码。
测试代码及结果
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,threadRun,(void*)"thread 1" );
void* ret=nullptr;
pthread_join(tid,&ret);//阻塞等待新线程 等待完才会往下进行
cout<<"main thread wait done , main quit..."<<endl;
return 0;
}
打印返回内容
1. 根据返回值的类型进行强转。注意Linux是64位环境,指针是八字节,不能用int接受。
cout<<(long long)ret<<endl;
2. 直接将ret定义为返回值的类型,在pthread_join中进行强转为(void**)&ret
void* pthreadRun(void* args)
{
int*dp[5];
//...
return (void*)dp;
}
int main()
{
//...
int* ret=nullptr;
pthread_join(tid,(void**)&ret);
}
2.3 线程退出
如果使用exit();整个进程将退出。
pthread_cancel
#include <pthread.h>
int pthread_cancel(pthread_t thread);
2.4 线程tid的获取
pthread_self
#include <pthread.h>
int main()
{
//...
cout<<pthread_self()<<endl;
}
2.5 __thread 关键字
如果有这样一个全局变量v,我们在线程执行的代码中++v,并打印v和v的地址,在主进程中同样打印v和v的地址。我们发现,主进程中的v和线程中的v是一个值。
但如果我们不想让二者共享这个v,我们就需要添加__thread关键字。
#include<pthread.h>
__thread int v=0;
__thread关键字:修饰全局变量,让每一个线程各自拥有一个全局的变量——线程的局部存储
2.6 分离线程
默认情况下,线程是可以通过pthread_join接口被等待的。否则无法释放资源,从而造成资源泄露。但如果我们不关心线程的返回值,join是一种负担,我们可以告诉系统,当线程退出的时候,自动释放资源。
#include <pthread.h>
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,但更建议线程自己分离.
pthread_detach(pthread_self());
一个线程如果分离了,就不能再被等待了,pthread_join()会返回错误码
3. pthread_t
是 unsigned long int 的typedef结果,本质上是地址。
为什么说tid是地址?
我们之前在讲线程的特点的时候,说线程要有独立的栈和寄存器等。那我们如何实现线程之间栈的独立性,划分栈空间吗?Linux系统中没有线程的概念,只有轻量级进程,所以如果划分了栈,就与这点相悖。所以独立的栈空间需要由用户层(库)来提供。
为了让线程更快的找到自己的用户层属性。