1.线程概念
在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列。在linux中,由于线程和进程都具有id,都需要调度等等相似性,因此都可以用PCB来描述和控制,线程含有PCB,没有独立的地址空间,和进程内的其他线程共享地址空间。如下图:
从资源的角度上看,进程是资源分配的基本单位,线程是cpu调度的基本单位。每一个控制线程pcb,我们都可以看成是执行流,执行流可以是线程,也可以是只有一个线程的进程,在Linux中,所有执行流我们都可以看成轻量级进程(LWP)。
2.线程控制函数
首先,我们要了解Linux没有真正意义上的线程,所有执行流都是LWP,因此,为了满足用户对线程使用的需求,Linux的线程库对LWP的接口进行了封装,我们把库里封装好的线程称为用户态线程。
1.pthread_create创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
第一个参数是输出型参数,输出新线程的用户级线程id,参数2:设置线程的属性,参数3:返回值和参数都为void* 类型的函数指针,参数4:函数参数。创建线程,执行传入的函数。
2.pthread_join等待线程
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
第一个参数是要等的线程id,第二个是线程执行后返回值 。
3. pthread_exit,pthread_cancel,pthread_self()
int pthread_exit()线程退出,exit()是进程退出。
int pthread_cancel(pthread_t thread)让某个进程退出
pthread pthread_self() 用于获取用户态线程的tid
4.代码
#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include<time.h>
#include <pthread.h> // 原生线程库的头文件
using namespace std;
// pthread_create(pthread_t &tid, nullptr, handlerTask, td);创建线程的接口
//pthread_join(tid, &ret);线程等待
// pthread_exit()线程退出
//pthread_cancel(tid)取消线程
pthread_self() 用于获取用户态线程的tid
void * handlerTask(void* args)
{
string s1=(const char*)args;
sleep(10);
cout<<"我是"<<s1<<" pid 是 "<<getpid()<<endl;
cout<<"我的用户级线程id是"<<pthread_self()<<endl;
string* s2=new string("haha");
pthread_exit(s2);
}
int main()
{
cout<<"我是主线程"<<" pid 是 "<<getpid()<<endl;
cout<<"我的用户级线程id是"<<pthread_self()<<endl;
const char* s1="新线程";
pthread_t tid;
pthread_create(&tid, nullptr, handlerTask, (void*)s1);
cout<<"新进程用户级id是"<<tid<<endl;
void *ret=nullptr;
pthread_join(tid, &ret);
cout<<"结果为"<<*((string*)ret)<<endl;
}
makefile 文件(注意一定要链接pthread库)
test1:test1.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f test1
运行结果:
5.线程分离
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread)
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离.
pthread_detach(pthread_self());
3.用户态线程的实现
在共享区上由pthead动态库来维护,封装了LWP,其中用户级进程的id就是虚拟地址。
4.资源问题
1.线程私有的:
- 线程的硬件上下文(cpu寄存器的值)
- 线程的独立栈结构
- 线程id
- 信号屏蔽字
- errno 信号屏蔽字
- 调度优先级
2.线程共享的
- 内存和地址空间(代码和全局数据)
- 文件描述符表
- 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
- 当前工作目录
- 用户id和组id
5.线程的优缺点
线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多(线程的地址内存和地址空间是共享的,只需要创间pcb)
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多(线程切换时由于数据共享,cache不需要重新加载,而进程切换需要重新加载)。
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点
- 性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了 不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
- 缺乏访问控制 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
- 编程难度提高 编写与调试一个多线程程序比单线程程序困难得多