linux线程 | 线程的概念
前言:本篇讲述linux里面线程的相关概念。 线程在我们的教材中的定义通常是这样的——线程是进程的一个执行分支。 线程的执行粒度, 要比进程要细。 我们在读完这句话后其实并不能很好的理解什么是线程。 所以, 本节内容博主将会带友友们理解什么是线程!线程和进程的关系等等。现在, 开始我们的学习吧!
ps:本节适合已经学习了进程的友友们进行观看哦。
目录
linux中的线程该如何理解
linux实现线程的方案
重新定义进程和线程
tcb
模拟线程
tcb与模拟线程的区别
如何分配线程
线程和进程的切换问题
linux中的线程该如何理解
首先我们知道我们的进程看待自己所能看到的所有资源都是通过地址空间来看的。 所以,地址空间是进程的资源窗口。所以我们的进程如果想做任何事情, 诸如加载动态库, 申请内存, 查看变量等等操作就必须使用地址空间加页表的方案, 从物理内存当中找到我们的代码和数据或者堆空间。 ——地址空间是进程的资源窗口。 如果我们今天cpu调度进程时, 就相当于task_struct在cpu所对应的运行队列中去排队。
另外, 在我们的子进程当中, 我们的子进程是拷贝一份父进程的task_struct和父进程的地址空间。 然后重新映射到物理空间的不同位置。 如同下图:

但是呢, 如果我们这么设计: 不再拷贝进程的PCB以及地址空间了。 只拷贝PCB, 然后让拷贝的PCB分走一部分进程地址空间的代码区, 堆区, 等等各种区。 然后呢, 创建多个PCB, 这些PCB都和原本的PCB共享一个地址空间。 (如何划分这个地址空间成为若干份, 由后续的设计者规定)。 那么我们就会发现我们新创建的进程以及曾经的进程, 他们可以对地址空间进行一定程度上的分享, 此时我们就可以认为新创建出来的这些新进程, 他们执行的粒度要比我们原本的进程要细一些。 并且, 又因为这些新进程执行的代码是一整个一整个代码区的一部分, 所以就称为它是一个分支, 我们为了区分这种进程, 就叫做线程。

linux实现线程的方案
在linux中, 线程在进程的地址空间运行(为什么?任何执行流要执行, 必须要有资源!而地址空间是进程的资源窗口。又因为线程是进程内部的一个执行流, 所以线程在进程的地址空间运行!)
在linux中, 线程的执行力度要比进程更细? 这是因为线程执行进程代码的一部分。
不同的操作系统对于线程的概念一定都是一样的, 但是不同的操作系统对于线程的管理或者线程的整体的实现方案是不一样的。 linux的实现方案是上面的样子。 所以, 站在cpu的角度, cpu知不知道我们现在正在访问task_struct是线程还是进程呢? 或者说cpu需不需要知道呢?——答案是不需要关心, cpu只有调度执行流的概念, 所以cpu只要访问代码就访问代码, 访问数据就访问数据。 cpu只要拿到代码和数据, 并不关心到底是进程还是线程。
重新定义进程和线程
- 线程:我们认为,线程是操作系统调度的基本单位!!
- 进程:进程是承担分配系统资源的基本实体!
(我们之前的理解是进程 = 内核数据结构task_struct+代码和数据 ------- v1)现在我们把整个的所有的线程, 以及地址空间, 页表和页表映射到的一点点物理内存叫做进程。 就如同下图:

在操作系统中, 我们分配资源的方式是以进程为单位进行分配的。 就比如线程, 那么操作系统创建task_struct, 然后分配一部分地址空间——执行流是资源吗? 执行流就是task_struct, 就是资源。
所以,进程和线程的关系就是:进程里面就是包含线程的。 因为进程是操作系统分配资源的基本实体, 而我们的线程是进程内部的执行流资源。
那么, 我们重新理解进程, 如何理解以前的进程呢?

操作系统以进程为单位为我们分配资源。 只不过我们当前的进程内部, 只有一个执行流!!——也就是说, 我们之前讲的才是特殊情况。 现在的多个线程是正常情况。
如果我们的一个进程内部真的有多个进程。 所以, 进程与线程的比率一定是1 : n的, 并且至少为1 : 1。 所以, 当进程执行的时候, 当前进程的状态是什么, 这个线程当前执行到什么位置了。 当前需要访问哪些资源, 期间要访问哪些资源。 这个线程是属于哪个进程的, 这个线程需要被切换吗, 什么时候被切换等等这些问题。那么我们知道, 线程一定比进程更多。 线程不是一创建就直接退出了。 也不是一创建就完成了。 是创建的时候就开始了, 操作系统要调用这个线程, 运行这个线程, 切换这个线程。 但是, 我们知道, 线程的个数是非常多的。 那么这么多的线程 , 这么多的操作需要执行。 所以操作系统要进行调度, 就一定需要管理起来。 而如何管理呢? 就是先描述再组织!
tcb
对于大部分操作系统来说, 就是描述struct tcb。 就比如windows。 对于线程来说, 线程是属于一个进程的。 而一个进程的线程有很多, 有需要组织起来, 那么我们操作系统就需要描述一个数据结构组织线程, 然后将这个数据结构和一个进程组织起来, 还要和调度队列联系起来。 如果我们的线程出现问题, 那么又会影响我们的进程, 影响我们的调度队列等等。 这就太复杂了。 但是呢, 又不得不这么干, 所以windows就这么干了, 也就是struct tcb;
模拟线程
对于计算机世界来说, 我们要管理线程, 那么就势必要先描述在组织。 但是linux程序员们并不想windows程序员们一样重新描述和组织线程。 没有人规定必须用新的描述方式和组织方式管理线程。 linux的设计者们考虑到既然线程也有上下文, 也有各种状态。 进程task_struct也有, 所以就没有必要再单独为进程重新描述,重新组织了。 所以linux的设计者们就直接服用进程数据结构和管理算法了。 ——struct task_struct——模拟线程。
那么这样做之后, 如何区分线程, 进程呢?——如果我们的数据结构内部有一份资源, 那么就是进程。 如果里面有多个PCB, 那么这个整体就是线程。 而且未来我们不区分这个PCB是不是进程, 是不是线程, 我们统一叫做执行流。 而什么叫做进程呢?页表、 地址空间、 所有的线程、 一点点物理地址这些合起来才是进程(分配资源的基本实体), PCB就是执行流, 只不过以前进程只有一个PCB, 也就是只有一个执行流, 而现在线程有多个PCB, 多个执行流。
上面的tcb, 和模拟线程是两钟描述线程的方案。 但是对于模拟线程这个方案, 因为进程和线程是类似的。 所以线程如果再重新描述一次就势必会造成程序的复杂, 当出现问题的时候就不好处理, 但是如果使用模拟线程的方法, 程序就会更加简洁。 出现的问题一定会更少, 维护成本降低, 所以他的健壮性一定要比所有的操作系统要强, 而且强的不是一丁半点。
tcb与模拟线程的区别
无论是使用方案1还是方案2,, 都是具体的实现方案。 那么说linux中没有真正意义上的线程, 不是真的说linux没有线程。 对于tcb来说, tcb是将线程的概念体现在了代码上面。 而我们的linux并没有将线程的概念体现在代码上面, 而是体现在了人们的大脑当中!我们的windows通过tcb遵守了线程的概念。 我们的linux也通过模拟线程遵守了线程的概念!!并且, 我们是使用进程的内核数据结构模拟的线程。
操作系统, 什么叫做操作系统, 我们在学校上的课, 实际上是规定一款操作系统应该是什么样子。 或者说一款操作系统在设计上应该符合什么概念。 它是一款操作系统设计的指导手册, 但是具体我们在实现的时候, 不同的操作系统有不同的方案。
题外话:我们的cpu在执行的时候, 当它看到一个pcb的时候, 执行一部分代码时, cpu执行的代码, 是进程的代码, 还是线程的代码呢? 其实cpu无法区分到底是进程还是线程。 但是站在上帝的视角我们知道, cpu执行的执行流, 一定是小于等于进程的。 在大部分OS中, 执行流小于进程就是线程, 等于就是进程(线程的粒度更细)。 在linux中的执行流被称为“轻量级执行流”。
在我们国家, 承担分配社会资源的基本实体是什么呢?——家庭。 自古到今, 我们的社会都是以家庭为单位来向社会进行资源申请的。 然后呢, 我们就会发现, 在我们的家庭里:我们的爷爷奶奶每天就是打太极拳, 逛公园, 或者看别的老人下棋啥的, 目的就是把自己的身体养好。 我们的爸爸妈妈呢, 每天就是工作, 赚钱养家。 小孩在家就是学习。 那么我们会看到, 在这个家庭里面, 每一个人天然的就会存在一种小人物。 但是, 不管每一个人有着怎么样的小任务, 他们一定会有一个共同的任务——把自己家的日子过好。 只不过每一个人为了把日子过好, 领取的任务是不同的。 所以我们把家庭整体叫做进程, 把里面的爸爸妈妈, 爷爷奶奶, 我们自己叫做一个线程。 每一个线程和每一个进程的关系就是:线程是在进程的内部进行, 而且每一个线程能够做什么, 和我们进程所拥有的资源是有关的!! 而如果这个家庭里只有一个人, 就是我们进程的情况!!
如何分配线程
如何分配线程, 这个就需要用到我们页表的知识。 我们要先谈页表结构, 页表的工作原理。 再来谈如何分配线程, 看下面一张图:

当我们的程序加载到物理内存的时候, 页表有映射,那么从物理内存当中读取到cpu里面的地址是什么呢? 读到的是虚拟地址。 cpu读到虚拟地址后, cpu再从地址空间找到对应的位置, 通过页表映射就能够执行这个进程了。
那么, 我们以32位为例, 谈一谈虚拟地址是如何转化到物理地址的。 如果在32位条件下, 虚拟地址有多少位的呢? 答案也是32位。 那么, 首先我们对应的虚拟地址有32位, 并且这里需要知道的是这32位虚拟地址不是一个整体。 这32位虚拟地址我们把它转化为了10 + 10 + 12。
而且, 页表也不是一整块的。 如果页表是一整块的, 我们知道, 页表中的条目最多2 ^ 32个, 如果是整块的, 那么我们一个条目假设有10字节, 那么我们的内存就爆了, 所以, 页表一定不是一整块的。 页表是分为三个板块, 其中有一级页表、二级页表、还有一个页框级别页表。 就如同下图:

什么意思呢, 意思就是说, 我们未来从cpu内读到的某个虚拟地址, 这个虚拟地址一共有32位。 假如这个虚拟地址是1000 0000 1000 0010 0100 1000 0010 0010. 那么它其实存放的规则是按照10 + 10 + 12来存放的。 如下:
并且把他们分成第一个部分, 第二个部分, 第三个部分。所以, 将来会用我们第一个部分查找我们的第一季页表。 用第二部分查找我们的第二级页表。 第三部分, 查找我们的最后一个页表。 而查找的过程就是先将对应的各个部分转化为十进程。 再由十进制找到三个页表中的下标索引。 而我们的页表的前20位, 对应的就是先查第一级页表, 再查第二级页表。 查完后, 就已经能够查找到我们的物理内存对应的页框了!!
然后, 我们的第三个表, 指向的就是页框内具体的偏移量。 也就是说, 我们通过二级页表查找到了具体的哪一个页框, 然后得到了这个页框的地址, 假如是0x0012ff40。 那么再通过最后12位来获取偏移量。 最后0x0012ff40 + 虚拟地址最后12位。 就能得到最终的结果。
那么我们思考一下, 这个时候的页框, 最大是多少呢?
我们知道我们的页表当中也有一些权限字段, 是可读还是可写等等。 一共会有1024个二级页表。 假设我们一个二级页表项是4个字节。 所以一个二级页表4 * 1024byte, 即4kb。 所以将来一个页表是可以放到一个页框里面的。 那么我们一个进程, 最多会有多少4kb呢? 因为页目录最多1024个, 所以我们最终要乘以1024. 即最多会有4 * 1024kb = 4MB。 那么4MB大不大呢? 这个空间挺大的, 但是这个空间和上面的虚拟地址空间比起来已经很小很小了。 而且, 我们一个进程, 会把整个地址空间用完吗? 不会的。另外, 我们的内核地址空间, 是不需要给每个进程都维护一份的。 并且, 我们的每个进程不一定把每个地址空间全部弄完。 所以, 大部分进程根本就不能把第一个一级页表全部用完。 所以我们想说的是, 二级页表不一定全部存在。 (在大部分情况下都是不全的。但是, 其实创建一个进程依旧是一个很“重”的工作。)
那么看这个问题, 这里有int a = 10; 按道理来说, int类型有4字节, 那么就有4个地址。 但是为什么&a只拿到了一个地址呢? 这是因为我们有类型的存在。 我们的int类型, 只需要知道首地址, 然后之后的4个字节就按照偏移量向后找就行。 c/c++里面的自定义的类,归根结底就是我们一大堆内置类型的集合。 所以, 对于自定义的累, 取地址我们就会拿到第一个地址。 所以我们的起始地址 + 类型 = 起始地址 + 偏移量(这也是x86cpu的特点)
所以, 如何理解资源分配? 线程资源分配的本质, 不就是分配地址的空间范围吗? 我们的线程分配资源, 本质就是把我们的地址空间划分一部分。 那么这个划分难不难呢? 怎么划分呢? 就比如我们有10个函数, 这个函数有没有地址? 代码有没有地址? 那么我们的某个线程使用了这个函数, 使用了某个代码, 那么他就天然的具有地址了。
线程和进程的切换问题
我们说线程比进程更加轻量化,为什么呢?
- a:创建和释放更加轻量化(生死问题)
- b: 切换更加轻量化(运行问题)
总结就是整个生命周期线程都比进程更加轻量化。
我们知道的是线程他自己肯定要有自己的上下文。 但是线程在切换的时候, 他对应的页表需要切换吗? 不需要。 地址空间需要切换吗? 不需要。 所以, 线程在切换的时候, 只是在局部切换, 它的页表和地址空间都不需要切换。 那么, 为什么说它的切换效率更高呢?

这个涉及到了cpu的知识。 就是当线程在运行的时候, 其实本质上就是进程在运行, 线程是进程的执行分支。在cpu当中, 除了有寄存器, cpu还会有一个硬件级别的cache。 cpu认为和物理内存交互太慢了, 所以就在自己里面集成了一块cache空间。 这块空间相对于寄存器很大, 相对于内存不大。 这部分cache被称为缓存的热数据。
那么, 线程切换, 在同一个进程内的线程切换, cache内的数据不需要或者说很少需要重新缓存。 但是进程切换, 那么cache内的数据要丢失并且重新缓存, 数据从冷变热, 需要花费时间。所以, 线程的切换要更加轻量, 而进程要更加重。
——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!
相关文章:
linux线程 | 线程的概念
前言:本篇讲述linux里面线程的相关概念。 线程在我们的教材中的定义通常是这样的——线程是进程的一个执行分支。 线程的执行粒度, 要比进程要细。 我们在读完这句话后其实并不能很好的理解什么是线程。 所以, 本节内容博主将会带友友们理解什么是线程&a…...
2024年软件设计师中级(软考中级)详细笔记【3】数据结构(下)(分值5分)
上午题第3章数据结构下部目录 前言第3章 数据结构【下】(5分)3.5 查找3.5.1 查找的基本概念【考点】3.5.2 静态查找表的查找方法3.5.3 动态查找表3.5.4 哈希表3.5.4.1 哈希表的定义3.5.4.2 哈希函数的构造方法3.5.4.3 处理冲突的方法 3.6 排序3.6.1 排序的基本概念3.6.2 简单排…...
WPF|依赖属性SetCurrentValue方法不会使绑定失效, SetValue方法会使绑定失效?是真的吗?
引言 最近因为一个触发器设置的结果总是不起效果的原因,进一步去了解[依赖属性的优先级](Dependency property value precedence - WPF .NET | Microsoft Learn)。在学习这个的过程中发现对SetCurrentValue一直以来的谬误。 在WPF中依赖属性Dependency property的…...
Windows搭建Java开发环境(Building a Java development environment on Windows)
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…...
用FPGA做一个全画幅无反相机
做一个 FPGA 驱动的全画幅无反光镜数码相机是不是觉得很酷? 就是上图这样。 Sitina 一款开源 35 毫米全画幅 (3624 毫米) CCD 无反光镜可换镜头相机 (MILC),这个项目最初的目标是打造一款数码相机,将 SLR [单镜头反光] 相机转换为 DSLR [数码…...
使用 Go 语言与 Redis 构建高效缓存与消息队列系统
什么是 Redis? Redis 是一个开源的内存数据库,支持多种数据结构,包括字符串、列表、集合、哈希和有序集合。由于 Redis 运行在内存中,读写速度极快,常被用于构建缓存系统、实时排行榜、会话存储和消息队列等高并发场景…...
springboot 整合spring ai实现 基于知识库的客服问答
rag 需求产生的背景介绍: 在使用大模型时,常遇到的问题之一是模型可能产生幻觉,即生成的内容缺乏准确性。此外,由于大模型不直接访问企业的专有数据,其响应可能会显得泛泛而谈,不够精准或具体,…...
云原生(四十九) | WordPress源码部署
文章目录 WordPress源码部署 一、WordPress部署步骤 二、创建项目目录 三、上传源码到WordPress 四、配置安全组 五、配置WordPress 六、访问WordPress WordPress源码部署 一、WordPress部署步骤 第一步:创建项目目录 第二步:上传源码到项目目…...
Spring Boot 集成 LiteFlow 实现业务流程编排
LiteFlow 是一款轻量级的流程编排框架,它允许开发者通过简单的配置方式,将复杂的业务流程分解为多个独立的节点,然后通过定义规则来编排节点,达到解耦业务逻辑、提高代码可维护性的目的 1. LiteFlow 的基本概念 在 LiteFlow 中,主要有以下几个概念: 节点 (Node):代表一…...
在 Android Studio 中引入android.os.SystemProperties
在 Android Studio 中引入android.os.SystemProperties 前言 网上有很多种方法,其中直接导入包的办法是行不通的,昨天自己发现问题后也踩了很多坑,现在把问题解决了也全面汇总了几种方法,确保可以百分百引入 1. layoutlib.jar包…...
代码随想录算法训练营总结
这几天一直有事情需要忙,所以现在来准备总结以下训练营的成果。 先说以下总体感受,非常值得!!! 从两个月前开始跟着每天看发布的任务,然后每天坚持打卡,收获还是很大的,从数组开始…...
【uniapp】使用uniapp实现一个输入英文单词翻译组件
目录 1、组件代码 2、组件代码 3、调用页面 4、展示 前言:使用uniapp调用一个在线单词翻译功能 1、组件代码 2、组件代码 YouDaoWordTranslator <template><view class"translator"><input class"ipttext" type"te…...
6. 继承、重写、super、final
文章目录 一、重新定义需求二、继承1. 继续分析2. 概念3. 代码① 父类② 子类③ 测试结果 4. 饿狼传说之多层继承① 概念② 代码 5. 多继承 三、方法的重写1. 情境2. 代码① 吃什么② 怎么叫(Override重写) 3. 小结 四、super1. 啃老2. 啃老啃到底 五、final1. 用途及特征2. 举…...
Redis 其他类型 渐进式遍历
我们之前已经学过了Redis最常用的五个类型了,然而Redis还有一些在特定场景下比较好用的类型 Redis最关键的五个数据类型: 上面的类型是非常常用,很重要的类型。 除此之外的其他类型不常用,只是在特定的场景能够发挥用处&#…...
科研绘图系列:R语言绘制SCI文章图2
文章目录 介绍加载R包导入数据图a图b图d系统信息介绍 文章提供了绘制图a,图b和图d的数据和代码 加载R包 library(ggplot2) library(dplyr) library(readxl) library(ggpmisc)导入数据 数据可从以下链接下载(画图所需要的所有数据): 百度网盘下载链接: https://pan.baid…...
ARM知识点三和串口代码的编写流程
ARM的一些常见问题 ARM 体系结构的主要特点是什么? 精简指令集 (RISC):ARM 采用 RISC 结构,指令集较小且简单,执行效率高。相比于复杂指令集 (CISC),RISC 更强调每条指令的执行速度。低功耗设计:ARM 处理…...
【unity踩坑】打开vs2022没有文字联想/杂项文件
unity打开vs2022没有文字联想 修改外置编辑器安装unity开发插件vs编辑器显示杂项文件 修改外置编辑器安装unity开发插件 参考 在unity项目里选择Edit-> Preferences->External Tools然后更换编辑器 在vs工具界面添加unity游戏开发选项。 重新打开还是有问题ÿ…...
WebGoat JAVA反序列化漏洞源码分析
目录 InsecureDeserializationTask.java 代码分析 反序列化漏洞知识补充 VulnerableTaskHolder类分析 poc 编写 WebGoat 靶场地址:GitHub - WebGoat/WebGoat: WebGoat is a deliberately insecure application 这里就不介绍怎么搭建了,可以参考其他…...
大数据-161 Apache Kylin 构建Cube 按照日期、区域、产品、渠道 与 Cube 优化
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...
uni-app使用v-show编译成微信小程序的问题
问题 在uni-app使用v-show语法编译成微信小程序会有一个问题 当我们设置成v-show"false" 在Hbuilder X里面确实没有显示 然后运行到 微信开发程序里面 发现显示了出来,说明设置的 v-show"false"没有起作用 解决办法 首先去uniapp官网查看v…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
