当前位置: 首页 > news >正文

Linux操作系统学习(线程基础)

文章目录

  • 线程的基础概念
    • 线程控制
    • 内核LWP和线程ID的关系

线程的基础概念

​ 一般教材对线程描述是:是在进程内部运行的一个分支(执行流),属于进程的一部分,粒度要比进程更加细和轻量化

​ 一个进程中是可能存在多个线程的,可能是1:1也可能是1:n,所以线程也应由OS管理,管理就离不开“先描述,再组织”,所以线程也应该类似PCB一样有线程控制块TCB,但Linux中没有专门为线程设计的TCB,而是用进程PCB来模拟线程

​ 通过某些函数方法,可以在同一个进程中只创建task_struct,让这些PCB共享同一个地址空间,把当前进程的资源(代码+数据)划分成若干份,让每个PCB使用执行一部分。

​ 站在CPU的角度,CPU只看PCB,不关心是否共享一份地址空间,此时CPU看到的PCB就是一个需要被调度的执行流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovbkmZxa-1677869543495)(G:\Typora\图片保存\image-20221222011520050.png)]

​ Linux中是用进程来模拟线程的,所以也叫轻量级进程(站在用户角度是线程),不用单独为线程设计算法,可直接用进程的一套相关方法,所以不用维护复杂的进程和线程的关系。OS只需要聚焦在线程间的资源分配上就可以了,所以CPU是只看PCB,不区分线程和进程。

进程与线程的区别:

  • 进程具有独立性,可以有部分资源共享(基于管道、ipc资源下)

  • 线程共享进程的数据,但是也有属于自己的数据

  • 进程是资源分配的基本单位,承担系统资源分配的基本实体

  • 线程是CPU调度的基本单位,承担进程资源一部分的基本实体

线程的优点:

  • 创建一个新的线程的代价比创建一个进程小得多。创建一个线程虽然也需要创建数据结构,但是并不需要重新开辟资源,只需要将进程的部分资源分配给线程。创建一个进程不仅需要创建大量数据结构,还需要重新创建资源。

  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作少。线程只是进程的部分资源,切换的资源少。

  • 线程占用的资源比进程少

  • 能充分利用多处理器的可并行数量

  • 在等待慢速的I/O任务结束的同时,程序可以执行其它的计算任务

  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。

  • I/O密集型应用,为了提高性能,将I/O操作重叠,线程可以同时等待不同的I/O操作。I/O操作是与外设交互数据,会很慢。

线程的缺点:

  • 性能缺失
    一个处理器只能处理一个线程,如果线程数比可用处理器数多,会有较大的性能损失,会增加额外的同步和CPU调度的开销,而资源却是不变的

  • 健壮性降低
    编写多线程时, 可能因为共享的变量, 导致一个线程修改此变量, 影响另外一个线程。(线程不是对立的,所以线程之间缺乏保护)

    ​ (多线程之间的变量是同一个变量;多进程之间变量不是同一个变量,一开始是共享父进程的,但在改变时发生写时拷贝)

  • 缺乏访问的控制
    进程时访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

  • 编程难度相对高

线程异常:

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃 ;线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,该进程内的所有线程也就随即退出

线程用途:

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是 多线程运行的一种表现)

线程与进程的关系

在这里插入图片描述


线程控制

​ 因为是用进程模拟的线程,所以Linux下不会给我们提供直接操作线程的接口,而给线程提供的是在同一个地址空间创建PCB的方法、分配资源给指定的PCB的接口等等,需要用户自己创建一些函数方法如创建线程、释放线程、等待线程等,对用户不太友好。

​ 所以一般我们在使用的线程相关函数方法,都是由系统级别的工程师在用户层对Linux轻量级进程提供的方法进行封装打包成的库。

下面主要介绍pthread库

  1. 创建线程函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wsdkuadx-1677869543496)(G:\Typora\图片保存\image-20221223164100513.png)]

  • threaad:输出型参数,会获取刚创建的线程ID
  • attr:传入的是线程的属性(优先级、运行栈等等)一般传入NULL,交给OS默认处理。
  • start_routine:创建线程后所执行的方法
  • arg:一般会传给第三个参数的函数方法
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>void* ThreadRun(void* args)
{std::string name = (char*)args;while(true){std::cout << "当前处于新线程,新线程ID是:" << pthread_self() << ", args: " << name << std::endl;sleep(1);}
}int main()
{pthread_t tid[5];for(int i = 0;i < 5;i++)pthread_create(&tid[i],nullptr,ThreadRun,(void*)"new thread");while(true){std::cout << "当前处于主线程,主线程ID是:" << pthread_self() << std::endl;std::cout << "###################################################################" << std::endl;for(size_t i = 0;i < 5;i++)std::cout << "创建的新线程[" << i << "]ID是:" << tid << std::endl;std::cout << "###################################################################" << std::endl;sleep(3);}return  0;
}

犹豫是多线程打印,会有乱码现象。

代码中用到的pthread_self(),作用是在谁的线程下用,就获取谁的线程 ID

在这里插入图片描述

​ 可以看到的是,他们的PID都是一样的,所以线程是对进程的资源分配 ,LWP是内核提供的,和打印出来的id是不一样的,这里用的是原生线程库,打出来的id是其实是进程地址号

  1. 线程等待

进程退出有三种方式

  • 代码正常运行,结果正确
  • 代码正常运行,结果不对
  • 程序异常,直接退出

​ 前两点退出时不会产生信号,但会产生退出码,而程序异常会产生终止信号;线程分配的资源是进程的一部分,所以只要有一个线程执行的代码部分导致程序崩溃,整个进程会直接退出并产生终止信号,整个进程崩溃,所有线程也就崩溃了。

​ 一般而言,线程也是需要等待的,否则也会出现类似僵尸进程那样的问题,已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,占用资源还得维护数据

​ 线程等待只能是在代码正常运行的前提下等待,而程序异常,进程就会直接退出了,OS会向进程发送退出信号,此时与线程无关,下图所示线程等待函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g8lH0kwN-1677869543496)(G:\Typora\图片保存\image-20221223182409990.png)]

  • thread:需要等待的线程的ID

  • retval:输出型参数,获取创建线程时指定的函数方法的返回值,(因为指定的函数方法返回值是指针返回,所以这里是二级指针)

    pthread_join函数在等待时是阻塞式等待

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>void* ThreadRun(void* args)
{sleep(3);return (void*)"finish";
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThreadRun,(void*)"new thread");void* status = NULL;pthread_join(tid,&status);std::cout << (char*)status << std::endl;return  0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVsAbkzU-1677869543496)(G:\Typora\图片保存\image-20221223182123417.png)]

  1. 线程退出

    线程退出的方法有三种

  • 函数中return

    • main函数return代表主线程和进程退出

    • 其他线程函数中return,代表当前线程退出

  • 通过pthread_exit函数终止线程

!](https://img-blog.csdnimg.cn/24870b7ee8624fc297d3f4b109cc4ce6.png)

参数是要传入的退出状态

void* ThreadRun(void* args)
{std::string name = (char*)args;std::cout << name << " running. . ." << std::endl;sleep(3);pthread_exit((void*)123);std::cout << "exit fail" << std::endl;return (void*)"finish";
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThreadRun,(void*)"new thread");void* status = NULL;pthread_join(tid,&status);printf("%d\n",(int*)status);return  0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wFkoQuxU-1677869543497)(G:\Typora\图片保存\image-20221223194429944.png)]

  • 通过pthread_cancel函数取消线程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FmTkx3LF-1677869543497)(G:\Typora\图片保存\image-20221223195240763.png)]

    只需要传入要取消的线程的id即可

    成功返回0,失败返回-1

    void* ThreadRun(void* args)
    {std::string name = (char*)args;std::cout << name << " running. . ." << std::endl;sleep(10);return (void*)"finish";
    }int main()
    {pthread_t tid;pthread_create(&tid,nullptr,ThreadRun,(void*)"new thread");sleep(1);std::cout << "cancel sub thread. . ." << std::endl;sleep(5);pthread_cancel(tid);void* status = NULL;pthread_join(tid,&status);printf("%d\n",(int*)status);return  0;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LhJGzBKU-1677869543497)(G:\Typora\图片保存\image-20221223195949508.png)]

    取消后的线程退出码是 -1 代表是PTHREAD_ CANCELED的意思

注意:不建议在子线程中取消主线程的做法,这样会导致进入僵尸进程的状态。因为主线程会退出而没有资源可以等待子线程退出,子线程就造成资源浪费了

void* ThreadRun(void* args)
{sleep(5);pthread_cancel((pthread_t)args);while(1){std::cout << " running. . ." << std::endl;sleep(1);}return (void*)"finish";
}int main()
{pthread_t tid;pthread_t g_tid = pthread_self();pthread_create(&tid,nullptr,ThreadRun,(void*)g_tid);void* status = NULL;pthread_join(tid,&status);printf("%d\n",(int*)status);return  0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6sFL15Cs-1677869543498)(G:\Typora\图片保存\image-20221223203927069.png)]

如图所示:进入了僵尸进程,以及类似僵尸进程的线程状态(主线程 defunct)

  1. 线程分离

若不想阻塞式的等待线程退出,可以使用线程分离函数;分离之后的线程不需要被join,运行完毕后会自动释放资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WDoqmLHI-1677869543498)(G:\Typora\图片保存\image-20221223222753176.png)]

传入要分离的线程id即可

void* ThreadRun(void* args)
{pthread_detach(pthread_self());std::cout << " running. . ." << std::endl;sleep(1);return (void*)"finish";
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThreadRun,(void*)"hello");sleep(3);void* status = NULL;int ret = pthread_join(tid,&status);printf("ret:%d,status:%s\n",ret,(char*)status);return  0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1F2IqD1-1677869543498)(G:\Typora\图片保存\image-20221223222813924.png)]

只要线程分离了,就不可在使用join去等待了,会等待失败

内核LWP和线程ID的关系

​ 我们使用的pthread原生线程库是存在磁盘当中的,在使用时会被加载到内存当中,通过页表映射到进程地址空间的共享区(堆区和栈区之间的区域)所有的进程都共用同一份库(只是进程空间中进程自己认为独占这个库)

​ 而每个线程共享的是同一份进程地址空间,但是每个线程也会有自己的私有数据部分如线程栈(主线程栈是在栈区的)、上下文数据、属性等等。这些线程私有的部分组成线程的数据块(可以想象成类似于task_struct的线程struct),数据块其实是存储在线程库中的,通过页表映射到进程地址的共享区。

(创建线程的时候,用mmap系统调用从heap分配出来的)

​ 在使用pthread_create创建线程时,线程库会生成一个ID号来表示新创建的线程,这个ID由函数的第一个参数可以获取到;而线程ID就是这些数据块在共享区映射的地址,每个线程id就是每个线程的数据块在共享区的地址,通过id号便能找到线程的所有数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1FzwW8l-1677869543498)(G:\Typora\图片保存\image-20221224021321882.png)]

线程id就是:线程存储在线程库中的数据,通过内存映射到进程地址空的共享区的地址标号,有了这个标号就能找到线程的所有的私有数据

LWP和线程id的关系是什么?

LWP是在内核层面的,线程id是在用户层面的。

​ 前面说过在Linux中没有提供专门的线程方法,使用进程模拟的线程,所以Liunx中的线程其实是轻量级进程,依旧用的task_struct,所以CPU调度知认PCB;那进程由PID,线程也该有个数字来标识这些task_struct,那这个标识就是LWP,所以LWP实际上是供CPU调度使用的(类似文件描述符一样)

​ 线程id是用户层的,LWP是内核层的,那id怎么知道他是对应哪个task_struct的呢?所以线程的数据块一定也包含一个LWP,形成1:1的关系(也有1:n,即一个线程id包含多个LWP,在用户层面看来是一个线程)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lfYM5XQC-1677869543499)(G:\Typora\图片保存\image-20221224023454692.png)]

相关文章:

Linux操作系统学习(线程基础)

文章目录线程的基础概念线程控制内核LWP和线程ID的关系线程的基础概念 ​ 一般教材对线程描述是&#xff1a;是在进程内部运行的一个分支&#xff08;执行流&#xff09;&#xff0c;属于进程的一部分&#xff0c;粒度要比进程更加细和轻量化 ​ 一个进程中是可能存在多个线程…...

YOLOv5源码逐行超详细注释与解读(1)——项目目录结构解析

前言 前面简单介绍了YOLOv5的网络结构和创新点&#xff08;直通车&#xff1a;【YOLO系列】YOLOv5超详细解读&#xff08;网络详解&#xff09;&#xff09; 在接下来我们会进入到YOLOv5更深一步的学习&#xff0c;首先从源码解读开始。 因为我是纯小白&#xff0c;刚开始下…...

前端开发总结的一些技巧和实用方法(2)

本文主要介绍一些JS中用到的小技巧和实用方法&#xff0c;可以在日常Coding中提升幸福度&#xff0c;也可以通过一些小细节来增加代码可读性&#xff0c;让代码看起来更加优雅&#xff0c;后续将不断更新1.数组 map 的方法 (不使用Array.Map) Array.from 还可以接受第二个参数…...

Docker搭建jenkins(Vue自动化部署)

前言 需要提前准备的条件 Docker环境 一、jenkins镜像 # 查询镜像 docker search jenkins# 下载镜像 # lts稳定版 docker pull jenkins/jenkins:lts#查看镜像 docker images二、启动Jenkins容器 创建挂载文件夹&#xff0c;并且进行文件授予权限 #创建文件夹 mkdir -p /home/j…...

ADCS攻击之CVE-2022–26923

CSDN自动博客文章迁移漏洞简介该漏洞允许低权限用户在安装了 Active Directory 证书服务 (AD CS) 服务器角色的默认 Active Directory 环境中将权限提升到域管理员。在默认安装的ADCS里就启用了Machine模板。漏洞利用添加机器账户&#xff0c;并将该机器账户dnsHostName指向DC[…...

AO3401-ASEMI低压P沟道MOS管AO3401

编辑&#xff1a;ll AO3401-ASEMI低压P沟道MOS管AO3401 型号&#xff1a;AO3401 品牌&#xff1a;ASEMI 封装&#xff1a;SOT-23 最大漏源电流&#xff1a;-4.2A 漏源击穿电压&#xff1a;-30V RDS&#xff08;ON&#xff09;Max&#xff1a;0.05Ω 引脚数量&#xff1…...

【STM32MP157应用编程】3.控制PWM

目录 PWM文件 指令操作PWM 程序操作PWM 程序说明 程序代码 3_PWM_1.c 启动交叉编译工具 编译 拷贝到开发板 测试 PWM文件 在/sys/class/pwm目录下&#xff0c;存放了PWM的文件。 pwmchip0和pwmchip4目录对应了MP157 SoC的2个PWM控制器&#xff0c;pwmchip0对应的是M…...

基于Python的selenium

一、安装 1.1安装Python&#xff0c;安装Python时需要勾选增加环境变量 如果之前已经安装过Python&#xff0c;需要将Python相关文件以及环境变量删除 1.2安装成功&#xff1a;在命令行界面下输入Python&#xff0c;最终展示>>>即可成功 2.1安装pycharm,直接自定义安装…...

Go底层原理:一起来唠唠GMP调度(一)

目录前言一、进程、线程、Goroutine1、进程与线程2、Goroutine二、Go调度器设计思想1、线程模型1.1 内核级线程模型1.2 用户级线程模型1.3 混合型线程模型2、 被废弃的 G-M 调度器2.1 了解 G-M 调度如何工作3、如今高效的 GMP 模型3.1 GMP模型调度流程3.2 GMP调度设计策略3.3 G…...

前端——1.相关概念

这篇文章主要介绍前端入门的相关概念 1.网页 1.1什么是网页&#xff1f; 网站&#xff1a;是指在因特网上根据一定的规则&#xff0c;使用HTML等制作的用于展示特定内容相关的网页集合 网页&#xff1a;是网站中的一“页”&#xff0c;通常是HTML格式的文件&#xff0c;它要…...

java四种线程池(基本使用)

标题java四种线程池及使用示例 1、线程工厂 1、我们先来写ThreadFactory&#xff0c;在创建线程池时候可以传入自定义的线程工厂&#xff0c;线程工厂说白了就是用来定制线程的一些属性&#xff1a;名字、优先级、是否为守护线程。直接看代码即可。 当然创建线程池的时候可以…...

float的表示范围为什么比long大

●很多人会有一个疑问, 一个用来表示小数的 float 为什么表示的范围会比 long 还要大呢 ? ●这次, 咱们就来详细说一说这个事情 从长计议 ●聊到这个话题, 我们就要从计算机存储数字这个位置说起了 ●计算机存储数字的方式其实就是 : 二进制 二进制是计算机中最基本的数字存储…...

Flutter Android 打包保姆式全流程 2023 版

大家好&#xff0c;我是 17。 为什么要写这篇文章呢&#xff1f;对于一没有 android 开发经验&#xff0c;从未有过打包经历的新人来说&#xff0c;要想成功打包&#xff0c;是很困难的。因为受到的阻碍太多&#xff0c;是完全陌生的领域&#xff0c;几乎是寸步难行。如果有老…...

C++笔记之lambda表达式

引言 Lambda表达式是从C 11版本引入的特性&#xff0c;利用它可以很方便的定义匿名函数对象&#xff0c;通常作为回调函数来使用。大家会经常拿它和函数指针&#xff0c;函数符放在一起比较&#xff0c;很多场合下&#xff0c;它们三者都可以替换着用。 语法 [ captures ] (…...

flink大数据处理流式计算详解

flink大数据处理 文章目录flink大数据处理二、WebUI可视化界面&#xff08;测试用&#xff09;三、Flink部署3.1 JobManager3.2 TaskManager3.3 并行度的调整配置3.4 区分 TaskSolt和parallelism并行度配置四、Source Operator(资源算子)五、Sink Operator(输出算子)六、Flink滑…...

Java面试题(二十三)DCL单例

懒汉式单例 private static SingletonInstance INSTANCE;private SingletonInstance(){}public static SingletonInstance getInstance() {if (INSTANCE null) {INSTANCE new SingletonInstance();}return INSTANCE;}构造方法私有化&#xff0c;然后判断是否为空&#xff0c;…...

UML-类图

一、类 一个类由三个格子组成&#xff0c;从上至下分别表示&#xff1a; 第一格&#xff1a;类名称&#xff08;接口和抽象类&#xff0c;使用斜体&#xff09; 第二格&#xff1a;类的属性&#xff08;成员变量&#xff0c;可以没有&#xff09; 第三格&#xff1a;类的操作&…...

PostgreSQL 数据库和 pgAdmin 4

PostgreSQL 数据库和 pgAdmin 4PostgreSQLPostgreSQL 数据库安装PostgreSQL 数据库安装 (Ubuntu)PostgreSQL 数据库其他系统安装PostgreSQL 数据库快速使用入门登录数据库访问数据库参考pgAdmin 4pgAdmin 4 安装使用 pgAdmin 4 登录数据库参考PostgreSQL PostgreSQL 数据库安装…...

quarkus 搭建与基础开发环境配置总结

quarkus搭建与基础开发环境配置总结 大纲 基础概念quarkus2.13.7脚手架工程配置配置maven3.8.7quarkus快速启动quarkus的三种打包方式quarkus将程序打包为二进制文件window环境下quarkus云原生二进制文件打包环境搭建使用GraalVM-java11替换本地java8运行二进制文件 基础概念…...

扩散模型DDPM开源代码的剖析【对应公式与作者给的开源项目,diffusion model】

扩散模型DDPM开源代码的剖析【对应公式与作者给的开源项目&#xff0c;diffusion model】一、简介二、扩散过程&#xff1a;输入是x_0和时刻num_steps&#xff0c;输出是x_t三、逆扩散过程&#xff1a;输入x_t&#xff0c;不断采样最终输出x_0四、具体参考算法流程图五、模型mo…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

R语言速释制剂QBD解决方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...