1.线程的优点:
• 创建⼀个新线程的代价要⽐创建⼀个新进程⼩得多
创建好线程只要调度就好了
• 与进程之间的切换相⽐,线程之间的切换需要操作系统做的⼯作要少很多
为什么?
◦ 最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上 下⽂切换的处理都是通过操作系统内核来完成的。
内核的这种切换过程伴随的最显著的性能 损耗是将寄存器中的内容切换出。
◦ 另外⼀个隐藏的损耗是上下⽂的切换会扰乱处理器的缓存机制。简单的说,⼀旦去切换上下 ⽂,处理器中所有已经缓存的内存地址⼀瞬间都作废了。还有⼀个显著的区别是当你改变虚 拟内存空间的时候,处理的⻚表缓冲 TLB (快表)会被全部刷新,这将导致内存的访问在⼀ 段时间内相当的低效。但是在线程的切换中,不会出现这个问题,当然还有硬件cache。
• 线程占⽤的资源要⽐进程少
• 能充分利⽤多处理器的可并⾏数量
线程是调度的基本单位
• 在等待慢速I/O操作结束的同时,程序可执⾏其他的计算任务
多进程也可以做,也是进程的优点
• 计算密集型应⽤,为了能在多处理器系统上运⾏,将计算分解到多个线程中实现
加密,解密,压缩。
通俗解释:计算机多个CPU,把计算任务拆分成多份,分别跑
最好只创建跟CPU对等的线程
• I/O密集型应⽤,为了提⾼性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程可以多创建。概率上总会有线程读数据,执行。
线程切换
(1)进程切换:PCB切成另一个进程的PCB。
task_struct*current:表示OS全局指针,current指向当前进程,寄存器。换跟换。
(2)线程切换:?
成本比进程低为什么?
同一个进程的线程切换:
不用保存CR3寄存器。------>看不出来谁的成本更低
页表不用切换。
为什么进程切换成本高???
进程切换的两个大缓存:
(1)对用户数据进行cache:
cache缓存:
Cache(缓存)是计算机系统中的一种高速存储器,位于CPU和主内存之间,用于暂时存储CPU可能频繁访问的数据和指令。Cache的主要作用是减少CPU访问主内存的次数,从而提高系统的整体性能。
Cache的访问速度非常快,通常在几个时钟周期内就可以完成数据的读取或写入。通过将CPU频繁访问的数据和指令存储在Cache中,CPU可以直接从Cache中读取数据,从而大大减少了访问主内存的次数,提高了系统性能(2)TLB快表
总结:
进化切换,会导致TLB和Cache失效,下次运行,需要重新缓存
线程不切换页表,不会出现缓存失效,所以线程切换成本更低。
2.线程的缺点
• 性能损失
◦ ⼀个很少被外部事件阻塞的计算密集型线程往往⽆法与其它线程共享同⼀个处理器。如果计 算密集型线程的数量⽐可⽤的处理器多,那么可能会有较⼤的性能损失,这⾥的性能损失指 的是增加了额外的同步和调度开销,⽽可⽤的资源不变。
• 健壮性降低
◦ 编写多线程需要更全⾯更深⼊的考虑,在⼀个多线程程序⾥,因时间分配上的细微偏差或者 因共享了不该共享的变量⽽造成不良影响的可能性是很⼤的,换句话说线程之间是缺乏保护 的。
• 缺乏访问控制
◦ 进程是访问控制的基本粒度,在⼀个线程中调⽤某些OS函数会对整个进程造成影响。
缺乏访问控制,也是优点,可以自由地访问资源
• 编程难度提⾼
◦ 编写与调试⼀个多线程程序⽐单线程程序困难得多
3.线程异常
• 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
• 线程是进程的执⾏分⽀,线程出异常,就类似进程出异常,进⽽触发信号机制,终⽌进程,进程 终⽌,该进程内的所有线程也就随即退出
Linux进程VS线程
进程和线程
• 进程是资源分配的基本单位
• 线程是调度的基本单位
• 线程共享进程数据,但也拥有⾃⼰的⼀部分“私有”数据::
◦ 线程ID
◦ ⼀组寄存器,线程的上下文
“线程要有自己的独立上下文数据 - >线程是可以被独立调度的。”
◦ 栈
线程都有自己独立的栈结构->线程是一个动态的概念(“有生命周期”)。
◦ errno “错误码”
◦ 信号屏蔽字
◦ 调度优先级
进程大部分资源独占,少量资源共享
线程相反。
进程被多个线程共享
同⼀地址空间,因此Text Segment、Data Segment都是共享的,如果定义⼀个函数,在各线程中都可以调 ⽤,如果定义⼀个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
• ⽂件描述符表
• 每种信号的处理⽅式(SIG_IGN、SIG_DFL或者⾃定义的信号处理函数)
• 当前⼯作⽬录
• ⽤⼾id和组id
进程和线程的关系如下图:
关于进程线程的问题
• 如何看待之前学习的单进程?具有⼀个线程执⾏流的进程
Linux线程控制
创建一个线程
功能:创建⼀个新的线程 原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
(1)参数: thread:返回线程ID attr:设置线程的属性,attr为NULL表⽰使⽤默认属性
(2)start_routine:是个函数地址,线程启动后要执⾏的函数
(3)arg:传给线程启动函数的参数
(4)返回值:成功返回0;失败返回错误码
代码
Makefile
test_thread:TestThread.cc
g++ -o $@ $^
.PHONY:clean
clean:
rm -f test_thread
TestThread.cc:自己写的√
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
using namespace std;
void* threadstat(void*args){
string name = (const char*)args;
while(true){
cout<<"我是子进程,name:"<<name<<endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,threadstat,(void*)"thread-1");
while(true){
cout<<"我是主线程"<<endl;
sleep(10);
}
}
编译不通过
不是系统调用
test_thread:TestThread.cc g++ -o $@ $^ -lpthread .PHONY:clean clean: rm -f test_thread
需要加上库pthread,-l:引入库文件的操作
编译通过:结果,没有子进程,两个死循环跑起来
只有一个进程,杀掉,两个都没了。
理解
ps -aL:查看线程
创建线程成功:
新线程执行:threadrun,新线程入口
编译出来就是一组虚拟地址main函数,就是另一组虚拟地址,表示代码和数据。
pid:是一样的,属于同一个进程
TTY:终端一样
LWP-light weight process:轻量级进程
“主进程,pid和lwp一样”
“线程pid和主进程一样,lwp与pid不一样”task_strut里面有两个数据
pid_t pid;
pid_t lwp;
问题
1.关于调度的时间片问题:时间片是等分给不同的线程。
2.线程异常??
代码:√
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
using namespace std;
void* threadstat(void*args){
string name = (const char*)args;
while(true){
cout<<"我是子进程,name:"<<name<<endl;
sleep(1);
int a = 1;
a/=0;
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,threadstat,(void*)"thread-1");
while(true){
cout<<"我是主线程"<<endl;
sleep(10);
}
}
任何一个线程崩溃,都会导致整个进程崩溃
3.消息混在一起?
显示器是共享资源
引入pthread库
为什么会有一个库?这个库是什么东西?
c++11多线程demo
c++11也引入了多线程叫做
thread
#include <iostream>
#include <string>
#include <thread>
void hello()
{
while (true)
{
std::cout << "新线程: hello world, pid: " << getpid() << std::endl;
sleep(1);
}
}
int main()
{
std::thread t(hello);
while (true)
{
std::cout << "我是主线程..." << ", pid: " << getpid() << std::endl;
sleep(1);
}
t.join();
return 0;
}
报错,由于没有使用原生线程库pthread.
test_thread:TestThread.cc g++ -o $@ $^ -std=c++11 - .PHONY:clean clean: rm -f test_thread
这样就OK
test_thread:TestThread.cc g++ -o $@ $^ -std=c++11 -lpthread .PHONY:clean clean: rm -f test_thread
c++11的多线程,在linux下,本质就是堆pthread的封装
c++,在不同的环境下,对线程进程不同的封装。
所有语言的线程都要独立进行封装
线程控制的接口
创建线程:pthread_create
功能:创建⼀个新的线程 原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
(1)参数: thread:返回线程ID attr:设置线程的属性,attr为NULL表⽰使⽤默认属性
(2)start_routine:是个函数地址,线程启动后要执⾏的函数
(3)arg:传给线程启动函数的参数
(4)返回值:成功返回0;失败返回错误码
终止线程:pthread_exit | pthred_cancle
如果需要只终⽌某个线程⽽不终⽌整个进程,可以有三种⽅法:
pthread_exit函数:
功能:线程终⽌
原型:
void pthread_exit(void *value_ptr);
参数:
value_ptr:value_ptr不要指向⼀个局部变量。
返回值:
⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是⽤malloc分配的, 不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
pthread_cancel函数
功能:取消⼀个执⾏中的线程 原型: int pthread_cancel(pthread_t thread);
参数: thread:线程ID
返回值:成功返回0;失败返回错误码
线程等待:pthread_join
为什么需要线程等待?
• 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
• 创建新的线程不会复⽤刚才退出线程的地址空间。
线程创建好之后,新线程要被主线程等待
---->不然会触发,类似僵尸进程,内存泄漏
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread:线程ID
value_ptr:它指向⼀个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
调⽤该函数的线程将挂起等待,直到id为thread的线程终⽌。thread线程以不同的⽅法终⽌,通过 pthread_join得到的终⽌状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ptr所指向的单元⾥存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调⽤pthread_cancel异常终掉,value_ptr所指向的单元⾥存放的是常 数PTHREAD_CANCELED。
3. 如果thread线程是⾃⼰调⽤pthread_exit终⽌的,value_ptr所指向的单元存放的是传给 pthread_exit的参数。
4. 如果对thread线程的终⽌状态不感兴趣,可以传NULL给value_ptr参数。
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstdio>
using namespace std;
int flag = 100;
void showid(pthread_t &tid)//减少拷贝
{
printf("tid: 0x%lx",tid);
}
string Formatid(const pthread_t &tid){
char id[64];
snprintf(id,sizeof(id),"0x%lx",tid);
return id;
}
void* threadstat(void*args){
string name = (const char*)args;
pthread_t id = pthread_self();
int cnt = 5;
while(cnt){
cout<<"我是子进程,name:"<<name<<"我的Id是"<<Formatid(id)<<endl;;
sleep(1);
cnt--;
flag++;
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,threadstat,(void*)"thread-1");
showid(tid);
int cnt = 6;
while(cnt--){
cout<<"我是主线程"<<"我的Id是"<<Formatid(pthread_self())<<"flag:"<<flag<<endl;
sleep(1);
}
void *ret = nullptr; // ret也是一个变量!!也有空间哦!
// 等待的目标线程,如果异常了,整个进程都退出了,包括main线程,所以,join异常,没有意义,看也看不到!
// jion都是基于:线程健康跑完的情况,不需要处理异常信号,异常信号,是进程要处理的话题!!!
pthread_join(tid, &ret); // 为什么在join的时候,没有见到异常相关的字段呢??
std::cout << "ret is : " << (long long int)ret << std::endl;
return 0;
}
返回值存放在ret里面,可以查看,长整型。
返回线程的tid:pthread_self
谁调用,就获取谁的tid
std::string FormatId(const pthread_t &tid)
{
char id[64];
snprintf(id, sizeof(id), "0x%lx", tid);
return id;
}
线程传参和返回值
主线程在调用FormatId,
新线程也会调用。
该函数被称为可重入函数
代码1:验证join可以去的线程执行完后的退出码/返回值
#include<iostream> #include<unistd.h> #include<pthread.h> #include<string> using namespace std; void* routine(void* arg){ string name = static_cast<const char*>(arg); int cnt = 5; while(cnt--) { cout<<"我是子线程,名字:"<<name<<endl; sleep(1); } return (void*)10; } int main(){ pthread_t tid; pthread_create(&tid,nullptr,routine,(void*)"thread-1"); int cnt = 5; while(cnt--){ cout<<"我是主线程"<<endl; sleep(1); } void* ret = nullptr; pthread_join(tid,&ret); cout<<"子进程退出,退出码为:"<<(long long)ret<<endl; }
1.main函数结束,代表主线程结束,也代表进程结束
2.新线程对应的入口函数,运行结束,代表当前线程运行结束。
3.问题:给线程传递的参数和返回值,可以是任意类型代码2:30min证明::给线程传递的参数和返回值,可以是任意类型 ,下面的例子是类类型
#include<iostream> #include<unistd.h> #include<pthread.h> #include<string> using namespace std; class Task{ public: Task(int a,int b) :_a(a) ,_b(b) {} int Cal(){ return _a+_b; } ~Task() {} private: int _a; int _b; }; class Result{ public: Result(int result) :_result(result) {} int getresult(){ return _result; } ~Result(){} private: int _result; }; void* routine(void* arg){ Task*t = static_cast<Task*>(arg); sleep(1); Result* r = new Result(t->Cal()); sleep(1); return r; } int main(){ pthread_t tid; Task* t = new Task(20,10); pthread_create(&tid,nullptr,routine,t); Result* ret = nullptr; pthread_join(tid,(void**)&ret); int n = ret->getresult(); cout<<"子线程的返回值是:"<<n<<endl; }