前置知识
堆区开辟空间原理
内存分割
1.exe就是一个文件
2.可执行程序是按照地址空间方式进行编译的
3.可执行程序是按照区域被划分为4KB为单位
结论:物理内存中的每个4KB的分格叫做页框,而磁盘中被分割成了4KB每个格子,那么加载进去是正好对应的,os中也要管理100w+个4KB,同样是先描述再组织。每次加载4KB的时候都会造成缺页中断,而用户却感觉不到,因为对用户是透明的。
虚拟到物理地址的转化
虚拟地址如何转到物理地址呢?那么可以地址编号32位后10位为1级页表,中间10位为2级页表,前面12位刚好为2^10=1024B=1KB再✖️4🟰4KB刚好和页框的大小相同,有多少那么就偏移多少位,所以前12位为页内偏移。
线程概念
这里的每个task_struct都可以称之为线程,这是linux特有的方案,通过一定的技术手段,将当前的资源,以一定的方式划分给不同的task_struct
用户视角:
进程=内核数据结构+该进程对的代码和数据
内核视角:
进程=承担分配系统资源的基本实体
线程在进程内部执行,是os调度的基本单位
线程在进程的地址空间内运行,cpu只关心pcb,不关心执行流水进程还是线程
进程vs线程
内部只有一个执行流的进程叫做进程,内部有多个执行流的进程叫做线程
在cpu视角中,只认task_struct,linux下的线程统称为轻量级进程,linux中说没有真正意义上的线程结构,linux用进程模拟的线程,但是linux中有原生线程库pthread
pthread_create
顾名思义创建线程
线程demo
只要创建了线程 那么就会抢占式的并发或者并行的执行
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<cstdlib>
#include<string>
using namespace std;
void* threadrun(void* arg){
const string s=(char*)arg;
while(true){
cout<<"string:"<<s<<" pid:"<<getpid()<<endl;
sleep(1);
}
}
int main(){
pthread_t tid[5];
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);
cout<<endl;
sleep(1);
}
while(true){
cout<<"main thread,pid:"<<getpid()<<endl;
sleep(3);
}
return 0;
}
查看线程数量
线程如何看待进程资源
线程中栈和寄存器是私有的,因为这代表了线程的动态属性
为什么线程切换成本更低?
首先,地址空间和页表不需要切换,cpu中有L1-L3级cache缓存,根据局部性原理对代码和数据进行预读。而进程切换需要更换cache中的预读数据,每次更换之前的数据就失效了,所以线程更有优势!
线程控制
pthread_join
pthread_join就是阻塞式等待,如果副进程不执行完毕,那么主进程就一直在这里可以不走。
代码验证
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_routine(void* arg){
int i=3;
while(true){
cout<<"this is new pthread---"<<(char *)arg<<" "<<getpid()<<endl;
sleep(1);
i--;
if(i==0){
break;
}
}
return (void*)10;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
void* ret=nullptr;
pthread_join(tid,&ret);
cout<<"ret:"<<(long long)ret<<endl;
return 0;
}
线程异常
一旦线程异常,都可能导致整个进程整体退出。因为线程可以相当于进程的子集,进程异常了,那么线程可以认为也是异常的
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_routine(void* arg){
int i=3;
while(true){
cout<<"this is new pthread---"<<(char *)arg<<" "<<getpid()<<endl;
sleep(1);
int i=100;
i/=0;
}
return (void*)10;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
void* ret=nullptr;
pthread_join(tid,&ret);
cout<<"ret:"<<(long long)ret<<endl;
return 0;
}
可以看到浮点数异常退出。
retval
pthread_join中第二个参数retval,可以看到是一个二级指针,那么这个参数必定是一个一级执政传过来的,这个参数就是线程回调函数的返回值。
这个返回值可返回类型很多,如nullptr,常量,变量,数组等
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_routine(void* arg){
int *arr=new int[10];
cout<<"this is new pthread---"<<(char *)arg<<" "<<getpid()<<endl;
for(int i=0;i<10;i++){
arr[i]=i;
}
return (void*)arr;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
int* ret=nullptr;
pthread_join(tid,(void**)&ret);
for(int i=0;i<10;i++){
cout<<ret[i]<<" ";
}
cout<<endl;
return 0;
}
由于这里是栈上所以要在堆区开辟出一块空间存放数据,否则可能会由于栈失效导致数据异常。
终止线程
pthread_exit
线程退出的一种方法
线程如果正常结束退出码为1,循环中结束退出码为13
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_routine(void* arg){
int i=5;
while(true){
cout<<"this is new pthread---"<<(char *)arg<<" "<<getpid()<<endl;
i--;
sleep(1);
if(i==0){
pthread_exit((void*)13);
}
}
cout<<"this is new pthread end"<<endl;
return (void*)1;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
void* ret=nullptr;
pthread_join(tid,&ret);
cout<<"ret:"<<(long long)ret<<endl;
return 0;
}
显然这里走的是pthread_exit函数 。
pthread_cancel
五秒后pthread_cancel关闭副线程
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_routine(void* arg){
while(true){
cout<<"this is new pthread---"<<(char *)arg<<" "<<getpid()<<endl;
sleep(1);
}
return (void*)1;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
void* ret=nullptr;
int i=5;
while(true){
cout<<"this is main pthread"<<endl;
sleep(1);
i--;
if(i==0){
break;
}
}
pthread_cancel(tid);
pthread_join(tid,&ret);
cout<<"ret:"<<(long long)ret<<endl;
return 0;
}
这里线程被取消,join的时候ret为-1。
pthread_t
目前所使用的linux自带的创建线程接口是使用的pthread库中的接口,那么pthread_t定义出的变量到底是什么呢?先说结论,本质上是一个地址。
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_routine(void* arg){
int i=5;
while(i--){
cout<<"this is new pthread---"<<(char *)arg<<" "<<getpid()<<endl;
sleep(1);
}
return (void*)1;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
printf("%lu,%p\n",tid,tid);
return 0;
}
pthread_self
取自己线程号的函数
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* pthread_routine(void* arg){
int i=3;
printf("%p\n",pthread_self());
return (void*)1;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
printf("%lu,%p\n",tid,tid);
while(true)sleep(1);
return 0;
}
可以发现该self函数和tid的地址相同。
原理
主线程的代码存在栈区,而副线程存储在共享区。
动态库加载到内存中,每次线程的使用就去动态库中调取里面的特定内容,而每个线程第一个地址就为tid也就是该tid的地址,往下还有一些局部存储变量,私有线程栈等。
__thread
__thread修饰全局变量,就是让每个线程各自拥有一个全局变量,这就是线性的局部存储
加上后
线程下的全局替换
全局替换后就直接退出了进程,因为给进程全体都替换了。
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
__thread int a=100;
void* pthread_routine(void* arg){
int i=3;
execl("/bin/ls","ls",nullptr);
while(true){
cout<<"a:"<<a++<<endl;
sleep(1);
}
return (void*)1;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
while(true){
cout<<"a:"<<a<<endl;
sleep(1);
}
return 0;
}
pthtrad_detach
线程分离的作用就是自动回收子线程
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
int a=100;
void* pthread_routine(void* arg){
pthread_detach(pthread_self());
int i=3;
while(true){
cout<<"this is child pthread"<<endl;
sleep(1);
i--;
if(i==0){
break;
}
}
return (void*)1;
}
int main(){
pthread_t tid;
pthread_create(&tid,nullptr,pthread_routine,(void*)"pthread 1");
int i=3;
while(true){
cout<<"this is main"<<(int *)pthread_self()<<endl;
sleep(1);
i--;
if(i==0){
break;
}
}
return 0;
}
线程互斥
订票问题中,当ticket数量大于0的时候才可以抢票,那么开几个线程去抢票。
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<ctime>
#include<string>
using namespace std;
#define THREAD_NUM 5
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
int ticket=100;
void* pthread_routine(void* arg){
while(true){
// pthread_mutex_lock(&mutex);
if(ticket>0){
usleep(1000);
cout<<(char*)arg<<":"<<ticket<<endl;
ticket--;
// pthread_mutex_unlock(&mutex);
}else{
// pthread_mutex_unlock(&mutex);
break;
}
}
return nullptr;
}
int main(){
pthread_t tid[THREAD_NUM];
for(int i=0;i<THREAD_NUM;i++){
string* s=new string("thread-"+to_string(i));
pthread_create(tid+i,nullptr,pthread_routine,(void*)s->c_str());
}
for(int i=0;i<THREAD_NUM;i++){
pthread_join(tid[i],nullptr);
cout<<tid[i]<<"quit"<<endl;
}
return 0;
}
可以发现最后的结果小于0也被输出了出来,这是由于cpu调度问题引起的。
pthread_mutex_lock和pthread_mutex_unlock
分别代表了线程中保护互斥安全问题,加锁和解锁函数
PTHREAD_MUTEX_INITIALITER
在进行全局使用锁的时候,那么可以直接申请锁,让锁的值等于该宏定义
pthread_mutex_init和pthread_mutex_destory
如果锁不在全局使用,那么就要给锁进行初始化和销毁锁。
锁的原理
当线程A去做操作ticket--,那么翻译成汇编会执行三句指令,将ticket的值从内存取来放入cpu中,对ticket的值进行--,然后再将数值存入内存中。理论上这样是可行的,但是由于cpu采用的是均匀分配的调度算法,那么执行a线程的时候b线程可能就被调度过来执行了,这里b线程可能会将ticket的三步全部做完,然后再将a线程的操作拿过来继续做,那么这样可能刚刚ticket的操作就可能白做了。
锁的原理其实就是一条指令,不像ticket--需要执行三条指令,exchage或者swap都算是,所以这也叫做原子操作。如汇编伪代码所示lock就是将0给&%al,交换%al和上下文中的mutex的值(1),如果%al寄存器中的值大于0,那么就持有锁成功,否则就挂起等待,然后goto到lock继续等待锁的释放,unlock就是将1移入mutex中,这样其他线程就可以申请锁了。
那么加锁可以有效的解决这种问题!
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<ctime>
#include<string>
using namespace std;
#define THREAD_NUM 5
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
int ticket=100;
void* pthread_routine(void* arg){
while(true){
pthread_mutex_lock(&mutex);
if(ticket>0){
usleep(1000);
cout<<(char*)arg<<":"<<ticket<<endl;
ticket--;
pthread_mutex_unlock(&mutex);
}else{
pthread_mutex_unlock(&mutex);
break;
}
}
return nullptr;
}
int main(){
pthread_t tid[THREAD_NUM];
for(int i=0;i<THREAD_NUM;i++){
string* s=new string("thread-"+to_string(i));
pthread_create(tid+i,nullptr,pthread_routine,(void*)s->c_str());
}
for(int i=0;i<THREAD_NUM;i++){
pthread_join(tid[i],nullptr);
cout<<tid[i]<<"quit"<<endl;
}
return 0;
}
但是发现都是线程4调度,这是为什么呢?这是因为抢占式的原因,线程4刚释放资源,又锁上资源。就造就了这种情况。
为了解决这一问题可以使用一些延长时间的函数去模拟抢到票后的工作,让其他进程可以抢占该资源
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<ctime>
#include<string>
using namespace std;
#define THREAD_NUM 5
int ticket=100;
class ThreadData{
public:
ThreadData(const string id,pthread_mutex_t* mutex)
:_id(id),
_mutex(mutex){}
public:
string _id;
pthread_mutex_t* _mutex;
};
void* getticket(void* arg){
while(true){
ThreadData* tid=(ThreadData*)arg;
pthread_mutex_lock(tid->_mutex);
if(ticket>0){
usleep(1000);
cout<<tid->_id<<":"<<ticket<<endl;
ticket--;
pthread_mutex_unlock(tid->_mutex);
}else{
pthread_mutex_unlock(tid->_mutex);
break;
}
usleep(1000);
}
return nullptr;
}
int main(){
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,nullptr);
pthread_t t[THREAD_NUM];
for(int i=0;i<THREAD_NUM;i++){
const string s="pthread-"+to_string(i);
ThreadData* tid=new ThreadData(s,&mutex);
pthread_create(t+i,nullptr,getticket,(void*)tid);
}
for(int i=0;i<THREAD_NUM;i++){
pthread_join(t[i],nullptr);
cout<<t[i]<<"quit"<<endl;
}
pthread_mutex_destroy(&mutex);
return 0;
}
互斥锁总结
1.加锁保护:加锁的时候,一定要保证加锁的粒度,越小越好。
2.加锁就是串行执行了吗?加锁了之后,线程在临界区中,是否会切换,会有问题吗?是的,就变成串行了,是会切换,但是由于原子性的体现,数据不会更改,所以没问题。
3.要访问临界资源,每一个线程都必须现申请锁,每一个线程都必须先看到同一把锁&&访问它,锁本身是不是就是一种共享资源?是的是共享资源。
4.谁来保证锁的安全呢??所以,为了保证锁的安全,申请和释放锁,必须是 原子的!
线程同步
同步概念
由于上面的代码某个线程中存在一直申请锁,一直解锁的情况,导致其他线程一直申请不到资源,这不错,但是不合理,自己线程太疲劳,别的线程一直申请不到,这就造成别的线程饥饿问题。
所以就引入了同步,主要是解决访问临界资源合理性的问题,按一定的顺序就行临界资源访问,进而线程同步
方案1:条件变量
当我们申请临界资源前,先要做临界资源是否存在检测,检测的本质也是访问临界资源,所以临界资源的检测,也是一定要在加锁和解锁之间的。
有没有办法让我们线程检测到资源不就序的时候?
1.不要让线程频繁自己检测,等待阻塞线程
2.当条件就序的时候,通知等待的线程,让他们来进行资源申请和访问!
pthread_cond_init
初始化对应的条件变量
pthread_cond_destroy
销毁对应的条件变量
PTHREAD_COND_INITIALIZER
当使用全局环境变量的时候,那么就可以使环境变量直接等于该宏。
互斥和同步demo
#include<iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
using namespace std;
#define PTHREAD_NUM 4
typedef void(*func_t)(const string& name,pthread_mutex_t*& mutex, pthread_cond_t*& cond);
volatile bool quit=false;
class PthreadData{
public:
PthreadData(const string& name,func_t func,pthread_mutex_t* mutex, pthread_cond_t* cond)
:_name(name),
_func(func),
_mutex(mutex),
_cond(cond)
{}
public:
string _name;
func_t _func;
pthread_mutex_t* _mutex;
pthread_cond_t* _cond;
};
void fun1(const string& name,pthread_mutex_t*& mutex, pthread_cond_t*& cond){
while(!quit){
pthread_mutex_lock(mutex);
pthread_cond_wait(cond,mutex);
if(!quit) cout<<name<<"running --播放"<<endl;
pthread_mutex_unlock(mutex);
}
}
void fun2(const string& name,pthread_mutex_t*& mutex, pthread_cond_t*& cond){
while(!quit){
pthread_mutex_lock(mutex);
pthread_cond_wait(cond,mutex);
if(!quit) cout<<name<<"running --下载"<<endl;
pthread_mutex_unlock(mutex);
}
}
void fun3(const string& name,pthread_mutex_t*& mutex, pthread_cond_t*& cond){
while(!quit){
pthread_mutex_lock(mutex);
pthread_cond_wait(cond,mutex);
if(!quit) cout<<name<<"running --刷新"<<endl;
pthread_mutex_unlock(mutex);
}
}
void fun4(const string& name,pthread_mutex_t*& mutex, pthread_cond_t*& cond){
while(!quit){
pthread_mutex_lock(mutex);
pthread_cond_wait(cond,mutex);
if(!quit) cout<<name<<"running --上传"<<endl;
pthread_mutex_unlock(mutex);
}
}
void* entry(void* arg){
PthreadData* tid=(PthreadData*)arg;
tid->_func(tid->_name,tid->_mutex,tid->_cond);
delete tid;
return nullptr;
}
int main(){
pthread_mutex_t mutex;
pthread_cond_t cond;
pthread_mutex_init(&mutex,nullptr);
pthread_cond_init(&cond,nullptr);
pthread_t tarry[PTHREAD_NUM];
func_t farry[PTHREAD_NUM]={fun1,fun2,fun3,fun4};
for(int i=0;i<PTHREAD_NUM;i++){
string s="pthread-"+to_string(i);
PthreadData* tid=new PthreadData(s,farry[i],&mutex,&cond);
pthread_create(tarry+i,nullptr,entry,(void*)tid);
}
int cnt=4;
sleep(2);
while(cnt--){
cout<<"running code.."<<cnt<<endl;
// pthread_cond_broadcast(&cond);
pthread_cond_signal(&cond);
sleep(1);
}
std::cout << "ctrl done" << std::endl;
quit = true;
pthread_cond_broadcast(&cond);
for(int i=0;i<PTHREAD_NUM;i++){
pthread_join(tarry[i],nullptr);
cout<<tarry[i]<<"quit"<<endl;
}
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
基于blockqueue cp模型
#include<iostream>
#include<pthread.h>
#include<queue>
using namespace std;
template<class T>
class BlockQueue{
public:
BlockQueue(int capacity)
:_capacity(capacity)
{ //分别对锁和信号量进行初始化
pthread_mutex_init(&_mutex,nullptr);
pthread_cond_init(&_full,nullptr);//判断是否为满
pthread_cond_init(&_empty,nullptr);//判断是否为空
}
void push(T& in){
pthread_mutex_lock(&_mutex);
while(FUll())pthread_cond_wait(&_full,&_mutex);
_bq.push(in);
pthread_cond_signal(&_empty);//给空信号量发送信号
pthread_mutex_unlock(&_mutex);
}
void pop(){
pthread_mutex_lock(&_mutex);
while(Empty())pthread_cond_wait(&_empty,&_mutex);
_bq.pop();
pthread_cond_signal(&_full);//给满信号量发信号
pthread_mutex_unlock(&_mutex);
}
int front(){
return _bq.front();
}
~BlockQueue()
{ //分别对锁和信号量进行销毁
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_full);
pthread_cond_destroy(&_empty);
}
private:
bool Empty(){
return _bq.size()==0;
}
bool FUll(){
return _bq.size()==_capacity;
}
private:
queue<T> _bq;
int _capacity;
pthread_mutex_t _mutex;
pthread_cond_t _full;
pthread_cond_t _empty;
};
#include"BlockQueue.hpp"
#include<unistd.h>
#include<time.h>
void* consumer(void* arg){
BlockQueue<int>* bq=(BlockQueue<int>*)arg;
while(true){
cout<<"this is consumer,i will get a data"<<bq->front()<<endl;
bq->pop();
sleep(2);
}
return nullptr;
}
void* producotr(void* arg){
BlockQueue<int>* bq=(BlockQueue<int>*)arg;
int flag=10;
while(flag--){
int x=rand()%100;
int y=rand()%50;
int z=x+y;
bq->push(z);
cout<<"this is productor i will calculate:"<<x<<"+"<<y<<"?"<<endl;
}
return nullptr;
}
int main(){
srand(time(nullptr)%getpid());
BlockQueue<int> *bq=new BlockQueue<int>(5);
pthread_t con[2];
pthread_t pro[2];
pthread_create(pro,nullptr,producotr,(void*)bq);
pthread_create(pro+1,nullptr,producotr,(void*)bq);
pthread_create(con,nullptr,consumer,(void*)bq);
pthread_create(con+1,nullptr,consumer,(void*)bq);
pthread_join(con[0],nullptr);
pthread_join(con[1],nullptr);
pthread_join(pro[0],nullptr);
pthread_join(pro[1],nullptr);
delete bq;
return 0;
}
锁的封装
#pragma once
#include<iostream>
#include<pthread.h>
class Mutex{
public:
Mutex(pthread_mutex_t* mutex):_mutex(mutex){}
void lock(){
pthread_mutex_lock(_mutex);
std::cout<<"加锁"<<std::endl;
}
void unlock(){
pthread_mutex_unlock(_mutex);
std::cout<<"解锁"<<std::endl;
}
~Mutex(){
}
private:
pthread_mutex_t* _mutex;
};
class lockGuard{
public:
lockGuard(pthread_mutex_t* mtx):_mtx(mtx)
{
_mtx.lock();
}
~lockGuard(){
_mtx.unlock();
}
private:
Mutex _mtx;
};