在谈多进程之前,我们在谈一谈页表,在语言中:char* str=”hello world”; *str=”H”;运行时会报错,原因在于:字符串在已初始化数据区和代码区之间的,需要写的时候,我们需要对str进行虚拟地址和物理地址的转换,查到了物理地址,继续查页表,然后查到了只有R权限,然后你的操作是写操作,MMU会将当前行为终止,向硬件报错,OS识别到硬件报错,把硬件操作转换为信号(段错误,11号信号),给进程发送11号信号,进程就要处理信号,信号的默认动作是终止,所以就终止了
如何看待地址空间和页表:
页表是如何从虚拟地址到物理地址的?
先查找虚拟地址的前10个比特位,索引页目录找到指定的页表项,再从虚拟地址的后10个比特,索引页表项找到物理内存的启始位置,到页框以后再从虚拟地址的后12位找到页内起始地址,剩下的根据类型,从起始地址处再继续向后找
**线程:**线程是OS能够进行运算调度的基本单位。线程是一个进程中的一个单一执行流,通俗地说,一个程序里的一个执行路线就叫做线程。
可以知道的是,一个进程至少有一个执行线程,这个线程就是主执行流。一个进程的多个执行流是共享进程地址空间内的资源,也就是说进程的资源被合理分配给了每一个执行流,这些样就形成了线程执行流。所以说线程在进程内部运行,本质是在进程地址空间内运行。
因为一个进程有多个线程,所以要对线程描述再组织,tcb,例如windows,单纯从线程角度,线程和进程有很多地方是重叠的,linux没有给线程设计专门的数据结构,而是直接服用pcb
Linux下没有真正意义上的线程,线程是通过进程来模拟实现的
进程是承担分配系统资源的基本实体,即一堆的task_struct,地址空间,页表,代码和数据,而线程就是创建多个task_struct,指向父进程的地址空间
没有真正的线程的好处是什么?
简单,维护成本降低
补充知识:虚拟内存决定了进程能够看到的“资源”
关系图:
1.pthread_create———创建一个线程
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:传给启动函数的参数(**这个是void *(start_routine) (void ),对应的参数)
返回值:
成功返回0,失败返回错误码
错误检查:
传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小
按照以前的编译是过不去的!因为linux没有真正意义上的线程,linux也就无法直接提供创建线程的系统调用接口!而只能给我们提供轻量级的进程接口!
如果我们使用的库不是语言上的接口,也不是操作系统的接口,是用户及线程库提供的,这是动静态链接所涉及的问题,-L找到动静态库在哪里, -I(大写i)找到头文件在哪里,-l(小写l)找到库名,这里的库名是phread,所以写成gcc -o #@ #^ -lpthread
实验:创建一个线程
#include
#include
#include
#include
#include
using namespace std;
int cnt=0;
//新线程
void* thread_routine(void* args)
{
char* name = (char*)args;
while(1)
{
// cout << "我是新线程,我正在运行! " << name << "cnt " << cnt << endl;
printf("我是新线程,我正在运行...,name=%s,cnt=%d\n", name,cnt);
sleep(1);
}
}
int main()
{
//创建线程tid
pthread_t tid;
int n=pthread_create(&tid,nullptr,thread_routine,(void*)"thread one ");
assert(n==0);
//主线程
while(1)
{
cnt ++ ;
// cout << "我是主线程,我正在运行!" << "cnt " << cnt << endl;
printf("我是主线程,我正在运行...,cnt=%d\n",cnt);
sleep(1);
}
return 0;
}
运行结果如下:
结论:每一个轻量级进程都有一个id,每一个轻量级进程id不同,但是pid是一样的!CPU调度的时候,以LWP为标识符标定一个特定的执行流,线程一旦被创建,几乎所有资源都是被所有线程共享的!
但同时线程也一定要有自己的私有资源,什么资源应该是私有的呢?
一个线程如果出现了异常,会影响其他线程,健壮性比较差,因为信号叫做进程信号,信号是整体发送给进程的
实验:创建多个线程
#include
#include
#include
#include
#include
#include
using namespace std;
//新线程
void* thread_routine(void* args)
{
char* name = (char*)args;
while(1)
{
printf("我是新线程,我正在运行...,name=%s\n", name);
sleep(1);
}
}
int main()
{
//1.创建一批线程
vector<pthread_t> tids;
#define NUM 10
for(int i=0;i<NUM;++i)
{
char namebuffer[64];
snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);
pthread_t tid;
pthread_create(&tid,nullptr,thread_routine,namebuffer);
}
//主线程
while(1)
{
printf("我是主线程,我正在运行...\n");
sleep(1);
}
return 0;
}
运行结果如下:
如果我们在pthread_create后面加上sleep(1);又会正常显示线程的编号,这是为什么呢?
当我们创建新线程谁先运行不确定,可能线程被创建出来了,还没来得及进行后续代码,继续跑主线程代码,主线程对缓冲区重新写入 ,你只是把缓冲区的地址传过去,但是缓冲区被下一次重新格式化显示覆盖了,所以是最新的编号
解决方法如下:
#include
#include
#include
#include
#include
#include
using namespace std;
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
//新线程
//这个函数被多个执行流访问,就是可重入函数!因为一个线程运行也没有影响另一个线程
//如果在函数里定义了变量,是局部变量,具有临时性,在多线程里也一样,这也证明了每一个线程都有独立的栈结构
void* thread_routine(void* args)
{
ThreadData* td = (ThreadData*)args;
while(1)
{
//不过这里的打印有问题,这里无法解释,后面解释
printf("我是新线程,我正在运行...,name=%s\n", td->namebuffer);
sleep(1);
}
delete td;
return nullptr;
}
int main()
{
//1.创建一批线程
vector<pthread_t> tids;
#define NUM 10
for(int i=0;i<NUM;++i)
{
//每一个线程创建都会new一个对象,把对象的地址传过去,不会共享buffer!
ThreadData* td=new ThreadData();
char namebuffer[64];
snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i);
pthread_create(&td->tid,nullptr,thread_routine,td);
}
//主线程
while(1)
{
printf("我是主线程,我正在运行...\n");
sleep(1);
}
return 0;
}
运行结果如下:
如果在线程中使用exit,会导致整个进程结束,因为exit是用来结束进程的而不是线程!
如果值想终止某个线程而不是整个进程,有三种方式:
1.pthread_exit函数———线程终止
void pthread_exit(void *retval);
参数:
retval:不能指向局部变量
实验:退出线程
#include
#include
#include
#include
#include
#include
using namespace std;
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
//新线程
void* thread_routine(void* args)
{
sleep(1);
ThreadData* td = (ThreadData*)args;
while(1)
{
printf("我是新线程,我正在运行...,name=%s\n", td->namebuffer);
sleep(1);
// return nullptr;
pthread_exit(nullptr);
}
}
int main()
{
//1.创建一批线程
vector<ThreadData*> threads;
#define NUM 10
for(int i=0;i<NUM;++i)
{
//每一个线程创建都会new一个对象,把对象的地址传过去,不会共享buffer!
ThreadData* td=new ThreadData();
char namebuffer[64];
snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i);
pthread_create(&td->tid,nullptr,thread_routine,td);
//方便后续处理
threads.push_back(td);
}
//主线程查看其他线程的创建情况
for(auto& it : threads)
{
cout << "create thread :" <<it->namebuffer << ": " << it->tid << "success" << endl;
}
//主线程
while(1)
{
printf("我是主线程,我正在运行...\n");
sleep(1);
}
return 0;
}
运行结果如下:
2.pthread_cancel函数———取消一个线程
前提是这个线程已经运行起来了!
int pthread_cancel(pthread_t thread);
参数:
thread:线程ID
返回值:
成功返回0,失败返回错误码
实验:主线程取消新线程
#include
#include
#include
#include
#include
#include
using namespace std;
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
class ThreadReturn
{
public:
int exit_code;
int exit_result;
};
void* thread_routine(void* args)
{
int cnt=3;
while(cnt--)
{
cout << "新线程" << endl;
sleep(1);
}
return (void*)16;
}
int main()
{
//1.创建一批线程
vector<ThreadData*> threads;
#define NUM 10
for(int i=0;i<NUM;++i)
{
//每一个线程创建都会new一个对象,把对象的地址传过去,不会共享buffer!
ThreadData* td=new ThreadData();
char namebuffer[64];
snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i);
pthread_create(&td->tid,nullptr,thread_routine,td);
//方便后续处理
threads.push_back(td);
}
for(int i = 0; i < threads.size()/2; i++)
{
pthread_cancel(threads[i]->tid);
cout << "pthread_cancel : " << threads[i]->namebuffer << " success" << endl;
}
//主线程
for(auto& it : threads)
{
int* ret=nullptr;
pthread_join(it->tid,(void**)&ret);
cout << "join :" <<it->namebuffer << "success,number" << (long long)ret <<endl;
delete it;
}
return 0;
}
运行结果如下:
线程不等待也会造成类似僵尸进程的问题———内存泄露
1.pthread_join函数———等待一个线程结束(阻塞式等待)
int pthread_join(pthread_t thread, void **retval);
参数:
thread:线程ID
retval:输出型参数,指向线程退出的返回值
返回值:
成功返回0,失败返回错误码
为什么没有所谓的退出信号的获取?因为线程出异常会全部退掉,只需要获取退出码
实验:回收线程
#include
#include
#include
#include
#include
#include
using namespace std;
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
//新线程
//这个函数被多个执行流访问,就是可重入函数!因为一个线程运行也没有影响另一个线程
//如果在函数里定义了变量,是局部变量,具有临时性,在多线程里也一样,这也证明了每一个线程都有独立的栈结构
void* thread_routine(void* args)
{
sleep(1);
ThreadData* td = (ThreadData*)args;
while(1)
{
printf("我是新线程,我正在运行...,name=%s\n", td->namebuffer);
sleep(1);
return nullptr;
}
}
int main()
{
//1.创建一批线程
vector<ThreadData*> threads;
#define NUM 10
for(int i=0;i<NUM;++i)
{
//每一个线程创建都会new一个对象,把对象的地址传过去,不会共享buffer!
ThreadData* td=new ThreadData();
char namebuffer[64];
snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i);
pthread_create(&td->tid,nullptr,thread_routine,td);
//方便后续处理
threads.push_back(td);
}
//主线程查看其他线程的创建情况
for(auto& it : threads)
{
cout << "create thread :" <<it->namebuffer << ": " << it->tid << "success" << endl;
}
//主线程
for(auto& it : threads)
{
pthread_join(it->tid,nullptr);
cout << "join :" << it->namebuffer << "success" <<endl;
//不能在新线程里delete,因为主线程和新线程的执行是随机的,可能已经被删除了,但是主线程才访问,会造成段错误
delete it;
}
return 0;
}
运行结果如下:
实验:线程等待获得退出结果
#include
#include
#include
#include
#include
#include
using namespace std;
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
void* thread_routine(void* args)
{
//一样的效果
// return (void*)16;
pthread_exit((void*)16);
}
int main()
{
//1.创建一批线程
vector<ThreadData*> threads;
#define NUM 10
for(int i=0;i<NUM;++i)
{
//每一个线程创建都会new一个对象,把对象的地址传过去,不会共享buffer!
ThreadData* td=new ThreadData();
char namebuffer[64];
snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i);
pthread_create(&td->tid,nullptr,thread_routine,td);
//方便后续处理
threads.push_back(td);
}
//主线程
for(auto& it : threads)
{
void* ret=nullptr;
pthread_join(it->tid,&ret);
cout << "join :" << it->namebuffer << "success,number" << (long long)ret <<endl;
}
return 0;
}
运行结果如下:
为什么二级指针可以这样使用?如图所示
如果设置了默认分离状态,不能够进行等待了!
1.pthread_detach函数———对一个线程进行分离
int pthread_detach(pthread_t thread);
参数:
thread:线程ID
返回值:
成功返回0,失败返回错误码
为了获得线程id我们还需要学一个函数
2.pthread_self()———获得线程id
pthread_t pthread_self(void);
实验:分离线程
#include
#include
#include
void* pthreadrun(void* arg)
{
int count = 0;
pthread_detach(pthread_self());
while (1){
printf("new threaad is running, pid is %d, thread id is %p\n", getpid(), pthread_self());
sleep(1);
if (++count == 3){
pthread_exit(NULL);
}
}
}
int main()
{
//pthread_t pthread[5];
pthread_t thread;
pthread_create(&thread, NULL, pthreadrun, NULL);
sleep(1);// 让线程先分离
if (pthread_join(thread, NULL) == 0){
printf("wait success\n");
}else{
printf("wait failed\n");
}
return 0;
}
运行结果如下:
1.进程切换:页表&&虚拟地址空间&&切换PCB&&上下文切换
2.线程(轻量级进程)切换:切换PCB&&上下文切换
3.CPU除了寄存器,还存在cache(CPU内的硬件级缓存),软件存在一个属性:局部性原理——当前正在访问的代码和数据附近的代码有较大的概率被访问到,如果当前进程内部在进行处理的数据的时候,访问到的代码和数据被预先访问到cache中,CPU在读取时先读取cache,如果cache没有命中,再去内存中读取,读取先缓冲到cache再读取,一个运行很久的进程,cache缓存了很多热点数据,线程在切换时,cache不用被切换,但进程切换,需要重新缓存,线程切换cache不用太更新,但是进程切换重新更新
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响
编写与调试一个多线程程序比单线程程序困难得多
共享:
进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
独立:
原生线程库中可能要存在多个线程,我们需要对线程进行管理,先描述(线程的属性比较少),再组织,每一次创建线程都要在pthread库中创建结构体,每一个结构体都会对应到一个轻量级进程的执行流
至于线程有独立的栈结构,具体如何实现的?如下图:
主线程用主线程的栈,其他线程用的栈在共享区,更准确点是在线程库中
如果我们就想再全局区创建变量,但是要求在线程独立的栈中我们可以在类型前加_thread,例如_thread int g_val=100;
虽然这个是用户级线程库,但是还是不够简单,所以我们可以封装起来
.hpp开源代码,类的生命和实现放在一起不用像之前.c .h分开写
Thread.hpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class Thread;
class context
{
public:
Thread* this_;
void *args_;
context()
:this_(nullptr)
,args_(nullptr)
{}
};
class Thread
{
public:
typedef function<void*(void*)> func_t;
const int num=1024;
Thread(func_t func,void* args,int number)
:func_(func)
,args_(args)
{
char buffer[num];
snprintf(buffer,sizeof(buffer),"thread-%d",number);
name_=buffer;
context *ctx=new context();
ctx->this_= this;
ctx->args_=args_;
int n=pthread_create(&tid_,nullptr,start_routine,ctx);
assert(n==0);
(void)n;
}
static void *start_routine(void* args) //类内成员,使用static没有this指针了
{
//把对应的方法再做一下包 装
context* ctx=(context*)args;
void* ret=ctx->this_->run(ctx->args_);
delete ctx;
return ret;
// 静态方法不能调用成员方法,把成员变量变量变为static可以,但不是好方法!
// return func_(args);
}
void* run(void* args)
{
return func_(args);
}
void join()
{
int n=pthread_join(tid_,nullptr);
assert(n==0);
(void)n;
}
~Thread()
{
//do nothing
}
private:
string name_;
pthread_t tid_;
func_t func_;
void* args_;
};
void *getTicket(void* args)
{
string work_type=(char*) args;
while(true)
{
cout << "我是一个新线程,我正在做:" << work_type << endl;
sleep(1);
}
}
test.cpp
#include"Thread.hpp"
int main()
{
Thread* thread1=new Thread(getTicket,(void*)"user1",1);
Thread* thread2=new Thread(getTicket,(void*)"user2",2);
Thread* thread3=new Thread(getTicket,(void*)"user3",3);
Thread* thread4=new Thread(getTicket,(void*)"user4",4);
// thread1->join();
// thread2->join();
// thread3->join();
// thread4->join();
while(1)
{
;
}
}