什么是线程
在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
一切进程至少都有一个执行线程
线程在进程内部运行,本质是在进程地址空间内运行在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程 执行流
线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点
- 性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
- 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
- 编程难度提高
编写与调试一个多线程程序比单线程程序困难得多
线程创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
参数
thread:返回线程ID attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数 返回值:成功返回0;失败返回错误码
获取线程ID
pthread_t pthread_self(void);
线程等待
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
创建新的线程不会复用刚才退出线程的地址空间。
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
创建并使用线程
void* startRoutine(void* arg)
{
char* name = (char*)arg;
int cnt=10;
while(cnt)
{
cout<<"我是线程"<<name<<" pthread id :"<< pthread_self() <<" cnt:"<< cnt << endl;
cnt--;
sleep(1);
}
//pthread_exit(0);
return nullptr;
}
int main()
{
pthread_t pth1;
pthread_t pth2;
pthread_t pth3;
//创建线程
pthread_create(&pth1,nullptr,startRoutine,(void*)"thread 1");
pthread_create(&pth2,nullptr,startRoutine,(void*)"thread 2");
pthread_create(&pth3,nullptr,startRoutine,(void*)"thread 3");
while(1)
{
cout<<"我是主线程"<< "main thread "<<" pthread id :"<< pthread_self()<<endl;
sleep(1);
}
return 0;
}
查看线程指令
ps -aL
监控线程
while :; do ps -aL | head -1 && ps -aL| grep mythread; sleep 1 ;done
分离线程
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
参数:
thread:被分离线程的ID。
void* startRoutine(void* arg)
{
char* name = (char*)arg;
int cnt=10;
while(cnt)
{
cout<<"我是线程"<<name<<" pthread id :"<< pthread_self() <<" cnt:"<< cnt << endl;
cnt--;
sleep(1);
}
pthread_exit(0);
return nullptr;
}
int main()
{
pthread_t pth1;
pthread_t pth2;
pthread_t pth3;
pthread_create(&pth1,nullptr,startRoutine,(void*)"thread 1");
pthread_create(&pth2,nullptr,startRoutine,(void*)"thread 2");
pthread_create(&pth3,nullptr,startRoutine,(void*)"thread 3");
pthread_detach(pth1);
pthread_detach(pth2);
pthread_detach(pth3);
while(1)
{
cout<<"我是主线程"<< "main thread "<<" pthread id :"<< pthread_self()<<endl;
sleep(1);
}
return 0;
}
线程ID及进程地址空间布局
线程ID,存放在第一个参数指向的地址
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
创建的新线程的空间存在动态库中,压栈操作在线程栈中,而不是虚拟地址空间的栈区