目录
一、线程的概念
1.1什么是线程
![](https://img-blog.csdnimg.cn/direct/a9d118d6316843a1b240f2e91622d00a.png)
1.2Linux中线程和进程的关系
在博主之前的文章中曾详细介绍过进程,进程=内核数据结构+代码和数据。而实际上要站在内核的角度:进程是分担系统资源的基本实体。进程实际上包含了PCB内核数据结构,进程地址空间,页表,以及其所映射到的物理内存
在以往的概念中进程执行代码是从上往下串行进行执行的,而线程则是可以并行执行的,A和B作为线程可以一起执行,互不干扰,而从理论角度上来讲线程也应该有自己的数据结构,而OS管理线程也应该是先描述再组织。而新建一个类并对其进行管理就意味着要设置一套与其匹配的算法。但Linux设计者考虑到这个问题,就将线程对进程进行了复用,从而减轻OS的复杂度。
所以Linux中线程就是直接对进程的数据结构来进行复用,所以在OS中,线程即所有被调度的执行流也被叫做轻量级进程。
而一个进程可以包含多个线程即轻量级进程,每个线程可以分别执行该进程中代码的不同部分。
1.3页表的结构及实现
页表是将虚拟地址转换为物理地址的重要结构,是OS中不可或缺的部分,而页表的实现并不像我们所认为的那样简单,一个地址对应一个真实的物理内存,博主以4GB的内存举例,如果采用这样的方式,32位平台下,需要开辟2^32-1个页表单元来映射2^32-1个虚拟地址,一个单元里包含一个虚拟地址以及一个真实的物理地址,一个地址4byte,也就是说一个页表单元8byte,乘上这么多的页表单元,整个页表就得30多个GB,内存直接爆掉,直接没得玩,而真正的页表是通过两部分来实现将虚拟地址转换为真实的物理内存:
上图是一个4btye的大小所能表示的内容,也就是32个比特位,而Linux中的页表在对这个虚拟地址转换成物理地址时,其前十位代表的是页目录,中间十位则是页表,后12位则是页内偏移量,前20位也被叫做页表项。
如此一来,32位平台下,页表最多有1024个(此处列举的是极限数据),而要想找到一个具体的物理地址,就可以根据页目录找到对应的页表项,再根据页表项找到对应的页框,然后加上后12位的页内偏移,就可以找到具体的物理内存,为什么是12位,因为2^12byte=2^10^2^2byte=1024^4byte=4KB。而在内存中刚好是以4KB为一个内存块进行划分的,所以每一个页表单元中存储的就是一个4KB内存块的起始地址也就是页框的地址。
找如上的方法,假设页目录和页表的单个单元都是4字节(32个比特位),那整个页目录也才4KB,假设每个页目录所对应的页表都是1024个单元每个也是4byte大小,那每个页表也才4KB,如此一来就大大减小了页表所占的空间。
二、线程的创建及使用
2.1pthread_t
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
创建线程
第一个参数:线程id
第二个参数:线程属性
第三个参数:返回值void*参数void*的函数指针(因为void类型可以接收任意类型,方便接收第四个参数)
注意:一般第三个参数拿void* args来接收,然后在函数内部通过
ThreadData *data=static_cast<ThreadData *>(args)的方式来将其转换为我们需要的类型
第四个参数:传递给线程要执行的函数(即第三个参数)的参数
接下来就通过下图的代码具体展示一下该函数的调用:
首先定义线程id,然后通过pthread_t创建一个新线程,暂时将第二和第四参数设为nullptr,将我们写的newthreadrun函数当作第三个参数进行传参。此时当前线程就会继续往下执行代码,而新创建的线程则会执行我们传过去的函数。
此时如果直接通过makefile文件去编译可执行程序我们会发现编译器报错,所以需要在编译时链接pthread库,所以需要在makefile文件中加上相关链接:
test_thread:testThread.cc
g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
rm -f test_thread
运行起来以后,我们可以发现,
查看轻量级进程,lwp就是light weight process,也就是轻量级进程,而操作系统调度时也是拿lwp来调度。当进程中只有一个执行流时,lwp=pid。这也叫做单执行流的进程。
所以,操作系统在进行调度的时候,就是通过lwp来调度的。
而一个.c文件进行编译后,其内部的函数就变成了若干代码块,每一行代码都有其虚拟地址(逻辑地址),而函数名则是该代码块的入口,最后形成的可执行程序,所有函数都会按照地址空间统一进行编址,而线程在进行并发执行时,各自就拿着各自的起始地址和结束地址去页表中找对应的物理内存然后执行对应的代码,从而就实现了一个进程中存在多个线程,各自去执行该代码的不同部分互不干扰。