主要介绍了为什么要有线程 和线程的调用 和简单的对线程进行封装。
背景知识
a.重谈地址空间
我们知道物理内存的最小单元大小是4kB
物理内存是4G那么这样的单元友1M个
操作系统先描述再组织struct page[1M]
对于32位数据字长的机器,页表有2^32条也就是4G条,每一条光保存两个地址加一个标记位 这个页表就得有36G这样大
实际上比如 0010 0010 0010 0010 0010 0010 0010 0010
虚拟地址>> 12 得到 页框号
虚拟地址&&oxfff 得到 页内偏移量
线程的概念
线程:在进程内部运行,是cpu调度的基本单位
进程:承担系统资源的基本实体
进程:承担系统资源的基本实体 怎么理解这句话呢?可以把进程看作家庭,国家的最小单位国是千万家~~家是社会资源分配的最小单位,把线程看作个人,线程是进程的一部分
os要单独设计线程 --新建?暂停?销毁?调度?线程要不要和进程产生关联
在windows 有 专门管理线程的数据结构
struct tcb
{
// 线程id
// 线程 优先级
// 状态上下文
// 链接属性
};
但是linux 中没有专门的线程,Linux 是用进程模拟的线程
linux中的执行流,我们称为轻量级进程
线程:在进程内部运行,是cpu调度的基本单位
在cpu看来被调用的都是轻量级进程
澄清进程和线程
进程 是 只有一个执行流的线程~
见一见线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_t *thread: 一个指向pthread类型的指针 , pthread 这个类型用于保存的线程的标识符
const pthread_attr_t *attr: 线程属性对象的引用,可以用来设置线程的优先级、调度策略等。如果不需要设置特殊属性,可以传入 NULL 使用默认属性。
start_routine: 指向线程开始执行的函数的指针这个函数的类型应为 void ()(void*),意味着它接受一个 void * 类型的参数,并返回 void * 类型的结果。
arg: 传递给 start_routine 函数的参数,类型为 void *。这使得你可以在创建线程时向线程函数传递数据。
#include <iostream>
#include <unistd.h>
void *threadStart(void * args)
{
while(true)
{
std::cout<<"new thraed running..."<<",pid:"<<getpid()<<std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
// 新线程
pthread_create(&tid, nullptr, threadStart,(void *) "thread-new"); // 相当于把页表分成了两部分
// 主线程
while(true)
{
sleep(1);
std::cout<<"main thraed running..."<<",pid:"<<getpid()<<std::endl;
}
return 0;
}
有了多进程为什么还要有多线程
线程不用再创建页表等,只需要一个pcb 创建的成本低
运行期间线程,的调度成本低,因为不需要切换页表
删除线程,比删除一个进程的成本低,只需要删除一个pcb就行了
线程的调度成本为什么更低呢?
我们切换页表只需要修改对应寄存器中的值就可以了,这个不是主要原因
最主要的原因是
数据会被缓存在cache中,一切换进程,cache中的数据就作废了,就需要重新cache了
为什么还要有进程呢?
线程的健壮性比较低,一个线程出错整个程序都退出了
哪些东西在多线程中是共享的
因为同一地址空间,所以代码段,数据段都是共享的,文件描述符表,每种信号的处理方式,工作目录。
哪些东西是各自都有一份的呢?
线程id,错误码,调度优先级,调度的上下文,信号屏蔽字
**最重要的:**一组寄存器,保存硬件上下文数据 线程是在调度运行的
栈:线程的栈主要用于存储函数调用的局部变量、函数参数以及返回地址。每当线程调用一个新的函数时,相关的局部变量和函数参数就会被压入该线程的栈中,形成一个新的栈帧。当函数返回时,相应的栈帧就会被弹出,恢复之前的调用环境。由于每个线程有独立的栈,它们可以并发地进行函数调用,而不会相互干扰。
几个基本的问题
问题1:主线程和新线程谁先运行?
系统会将这个新线程加入到就绪队列中等待调度。调度器会根据其自身的算法(比如时间片轮转、优先级等)来决定哪个线程获得 CPU 时间片并开始执行。
问题2:我们期望谁最后退出? main thread,你如何保证呢?
我们希望主线程后退出,如果主线程先退出,主线程退出了整个进程就结束了,可能此时新线程还没有完成任务呢。
我们用pthread_join 来保证 主线程后退出
void * RunThread(void * args)
{
std:: string name;
name = (const char *)args;
std::cout <<name << std::endl;
return args;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, RunThread,(void *)"thread - 1");
int n = pthread_join(tid,nullptr);
if(n == 0)
{
std::cout<<"wait success"<<std::endl;
}
return 0;
}
问题3: tid 是什么样子的?是什么呢?虚拟地址! 为什么?
void PrintToHEX(pthread_t tid)
{
char message[1024];
snprintf(message,sizeof(message),"0x%lx",tid);
std::cout<<message<<std::endl;
}
我们打出来发现是一个地址
为什么是一个地址呢?我们等下再说
问题4:全面看待线程函数传参: 我们可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!
class ThreadData
{
public:
int x;
int y;
};
void * RunThread(void * args)
{
std:: string name;
ThreadData * data = (ThreadData *)args;
std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
return args;
}
void PrintToHEX(pthread_t tid)
{
char message[1024];
snprintf(message,sizeof(message),"0x%lx",tid);
std::cout<<message<<std::endl;
}
int main()
{
pthread_t tid1;
ThreadData x1;
x1.x = 10;
x1.y =20;
pthread_create(&tid1, nullptr, RunThread,(void *)&x1);
// PrintToHEX(tid);
int n = pthread_join(tid1,nullptr);
if(n == 0)
{
std::cout<<"wait success"<<std::endl;
}
return 0;
}
问题6:注意刚刚我们x1的写法其实是不标准的 为什么?
#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:
int x;
int y;
};
void * RunThread1(void * args)
{
std:: string name;
ThreadData * data = (ThreadData *)args;
data->x = 5;
data -> y = 10;
std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
return args;
}
void * RunThread(void * args)
{
std:: string name;
ThreadData * data = (ThreadData *)args;
std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
return args;
}
void PrintToHEX(pthread_t tid)
{
char message[1024];
snprintf(message,sizeof(message),"0x%lx",tid);
std::cout<<message<<std::endl;
}
int main()
{
pthread_t tid1;
ThreadData x1;
x1.x = 10;
x1.y =20;
pthread_create(&tid1, nullptr, RunThread,(void *)&x1);
pthread_t tid2;
pthread_create(&tid2, nullptr, RunThread1,(void *)&x1);
int n = pthread_join(tid1,nullptr);
if(n == 0)
{
std::cout<<"wait success"<<std::endl;
}
return 0;
}
比如说这样,我们原本想要打印 10 ,20 5,10 的 结果却这样。
因为main 栈上的变量 这两个线程都可以看到并修改,且这个变量只有一份,当第一个线程正准备打印的时候,第二个线程修改了这两个变量的值。导致第一个线程打印出来就和第二个线程一样了。 根本原因就是 变量只有一份,我们多创建一份就好了。
而且我们不推荐直接 将main栈上的变量给新线程因为 main函数栈的变量生命周期是随main函数的,可能新线程早都结束了,但是main栈上的变量就是不结束
我们用new 可以在新线程中方便管理 传过来的变量的生命周期。
问题5: 全面看待线程函数返回
它也可以传递自定义类型哦
#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:
int x;
int y;
int Excute()
{
return x + y;
}
};
class ThreadResult
{
public:
int x;
int y;
int ans;
void print()
{
std::cout<<std::to_string(x)+"+"+std::to_string(y)+"="+std::to_string(ans)<<std::endl;
}
};
void * RunThread(void * args)
{
std:: string name;
ThreadData * data = (ThreadData *)args;
//std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
ThreadResult * result = new ThreadResult();
result->ans = data->Excute();
result->x = data->x;
result->y = data->y;
return (void*)result;
}
void PrintToHEX(pthread_t tid)
{
char message[1024];
snprintf(message,sizeof(message),"0x%lx",tid);
std::cout<<message<<std::endl;
}
int main()
{
pthread_t tid1;
ThreadData * x1 = new ThreadData();
x1->x = 10;
x1->y = 20;
pthread_create(&tid1, nullptr, RunThread,(void *)x1);
ThreadResult * result =nullptr;
int n = pthread_join(tid1,(void**)&result);
result->print();
if(n == 0)
{
std::cout<<"wait success"<<std::endl;
}
return 0;
}
问题6: 如何创建多线程呢?
我们通过循环的方式 创建多线程哦~~
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
void * RunThread(void * args)
{
std::string name = (const char *) args;
std::cout<<name<<std::endl;
return args;
}
int main()
{
for(int i = 0; i < 10; i++)
{
pthread_t tid;
char name[1024];
snprintf(name,sizeof(name),"this is %d new thread",i+1);
pthread_create(&tid, nullptr , RunThread,name);
}
sleep(100);
return 0;
}
我们发现结果并不符合预期。 因为name 只有一份,主线程 运行的时候不停在覆盖 name。
我们用 new ,循环 new 十次 主线程分配的时候每一个线程都得到 一个地址,那个地址指向一个独立的堆空间,线程之间就不互相影响了。
ok 完美解决~~!!!
问题7: 新线程如何终止?
1.我们可以在新线程中用return
2.我们可以在新线程中用pthread_exit 注意exit 是终止整个进程。
3.我们可以在新线程中使用pthrad_cancel
线程被取消线程的退出结果是:-1 #define PTHREAD_CANCELED ((void *) -1)
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>
void *RunThread(void *args)
{
while (true)
{
std::string name = (const char *)args;
std::cout << name << std::endl;
sleep(3);
}
return args;
}
int main()
{
std::vector<pthread_t> tids;
for (int i = 0; i < 10; i++)
{
pthread_t tid;
// char name[1024];
char *name = new char[1024];
snprintf(name, 1024, "this is %d new thread", i + 1);
pthread_create(&tid, nullptr, RunThread, name);
tids.push_back(tid);
}
for (auto tid : tids)
{
// pthread_detach(tid);
pthread_cancel(tid);
void *ret = nullptr;
int n = pthread_join(tid, &ret);
std::cout << (long long int)ret << " quit.." << std::endl;
// std::cout<<"n="<<n<<std::endl;
}
return 0;
}
pthread_cancel 是请求取消另一个线程,而 pthread_exit 是线程自身决定退出。
某个线程调用了exit 整个进程就结束了~
exit 你走错片场了啊。要用pthread_exit
void * RunThread(void * args)
{
std::string name = (const char *) args;
std::cout<<name<<std::endl;
pthread_exit(args);
return args;
}
问题8: 可以不可以不join线程,让他执行完就退出呢??可以!
我们用pthread_detach 就可以了,然后线程变成unjoinable 状态,如果此时再等待就会出错 然后终止整个进程。
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>
void *RunThread(void *args)
{
while (true)
{
std::string name = (const char *)args;
std::cout << name << std::endl;
sleep(3);
}
return args;
}
int main()
{
std::vector<pthread_t> tids;
for (int i = 0; i < 10; i++)
{
pthread_t tid;
// char name[1024];
char *name = new char[1024];
snprintf(name, 1024, "this is %d new thread", i + 1);
pthread_create(&tid, nullptr, RunThread, name);
tids.push_back(tid);
}
for (auto tid : tids)
{
pthread_detach(tid); // 主线程分离新线程,新线程必须存在
}
for (auto tid : tids)
{
// pthread_detach(tid);
pthread_cancel(tid);
void *ret = nullptr;
int n = pthread_join(tid, &ret);
std::cout << "n:"<<n<<std::endl;
// std::cout<<"n="<<n<<std::endl;
}
return 0;
}
封装线程
#include <string>
#include <pthread.h>
#include <functional>
namespace ThreadModel
{
class Thread
{
typedef void (*func_t)(std::string); // ?
// using func_t = std::function<void()>;
public:
Thread(const std::string & name,func_t func)
:_name(name),_func(func)
{}
~Thread()
{
}
void Excute()
{
_isrunning = true;
_func(_name); // 这里传了线程名
_isrunning = false;
}
static void * Routine(void * args) // 不用static 第一个参数是this
{
Thread * self = static_cast<Thread*>(args);
// self->_func();
self->Excute();
return nullptr;
}
void start()
{
pthread_create(&_tid,nullptr,Routine,(void *)this);
}
void join()
{
// std::cout<<_isrunning<<std::endl;
// std::cout<<true<<std::endl;
if(_isrunning) // 只有在运行中的线程才需要被等待
{
//std::cout<<"..."<<std::endl;
pthread_join(_tid,nullptr);
}
}
// 终止线程
void stop()
{
if(_isrunning)
{
_isrunning = false;
pthread_cancel(_tid);
}
}
private:
std::string _name;
pthread_t _tid;
func_t _func;
bool _isrunning; // 主要 用于线程终止时 只有启动了的线程 才能终止
};
}