跳到主要内容

为什么要重用线程

· 阅读需 2 分钟
素明诚
Full stack development

减少开销:线程的创建和销毁涉及内存分配和释放,以及相关系统资源的初始化和清理,这些都是有时间和空间开销的。通过重用线程,可以避免这些开销,从而提高系统效率。

提高响应速度:现成的空闲线程可以立即被分配来处理新的任务,避免了线程创建的延时,从而提高了系统的响应速度。

降低内存碎片化:频繁的内存分配和释放可能导致内存碎片化,影响内存利用率。线程重用通过减少内存分配和释放频率,有助于降低内存碎片化。

节约系统资源:每个线程的创建都会占用一定的系统资源。线程重用可以节约这些资源,提升系统的扩展性和稳定性。

package main

import (
"fmt"
"sync"
)

// 任务
type Task struct {
id int
}

// worker函数,处理任务
func worker(id int, tasks <-chan Task, wg *sync.WaitGroup) {
defer wg.Done() // 在函数退出时,通知WaitGroup一个任务已完成

for task := range tasks { // 循环从任务通道接收任务并处理
fmt.Printf("Worker %d processing task %d\n", id, task.id)
}
}

func main() {
const numWorkers = 5 // 定义worker数量
const numTasks = 10 // 定义任务数量

tasks := make(chan Task, numTasks) // 创建任务通道
var wg sync.WaitGroup // 创建一个WaitGroup以等待所有任务完成

// 启动worker goroutines
for i := 1; i <= numWorkers; i++ {
wg.Add(1) // 为每个worker增加一个任务计数
go worker(i, tasks, &wg) // 启动worker
}

// 将任务发送到任务通道
for i := 1; i <= numTasks; i++ {
tasks <- Task{id: i}
}

close(tasks) // 关闭任务通道,通知所有worker没有更多任务
wg.Wait() // 等待所有任务完成
}

什么是轮转时间片Round Robin Time Slice

· 阅读需 3 分钟
素明诚
Full stack development

轮转时间片(Round Robin Time Slice,通常简称为时间片或时间量)是操作系统中一种常用的进程或线程调度算法的核心概念

在轮转(Round Robin, RR)调度算法中,每个进程(或线程)被分配一个固定大小的时间片,以在 CPU 上执行。当一个进程的时间片用完时,操作系统会将 CPU 分配给下一个等待执行的进程。这个过程会不断重复,直到所有进程都完成执行

为什么会有轮转时间片

  • 公平性:为了防止某个进程独占处理器资源,需要一种方法能够平等地将处理器时间分配给每一个进程,这样每个用户或任务都能得到一定的服务时间。
  • 并发:随着计算需求的增长,系统需要能够同时处理多个任务。轮转时间片调度允许系统通过在进程之间快速切换来模拟并发处理,提高资源利用率和系统吞吐量。
  • 响应时间:对于需要快速响应的应用,通过使用时间片,系统可以在有限的时间内服务多个请求,减少了单个长任务造成的延迟。

应用场景

如果设计一个腾讯会议可能会遇到处理多个任务的场景,例如

  1. 视频压缩/解压缩:实时压缩来自发言者的视频以及解压缩发送给各个参与者的视频。
  2. 音频处理:包括噪声消除、回声消除和音频压缩/解压缩。
  3. 数据传输:管理各种会话的数据包的发送和接收。
  4. 用户界面响应:响应用户的各种交互,如静音/取消静音、打开/关闭视频、聊天等。

在这样一个系统中,如果采用轮转时间片调度,操作系统或应用服务器可以这样工作:

  1. 分配时间片:系统为每个活动任务(例如,不同的视频流、音频处理任务或数据传输会话)分配一个固定的时间片。
  2. 任务切换:当一个任务的时间片用完(或任务完成)时,调度器保存其状态(上下文切换),并将处理器控制权转移给下一个任务。
  3. 平衡服务:通过这种方式,系统确保没有单个任务或会话占用所有资源,每个并发会话都获得适当的处理时间,从而保持流畅的视频和音频输出质量。
  4. 减少延迟:此策略还有助于减少用户感知到的延迟,因为所有关键操作(如视频显示、音频播放和用户输入)都获得了定期的、相对均匀的处理时间。

这种任务调度方法允许视频会议系统高效、公平地管理多个并发会话,即使在高负载情况下也能保持性能和响应速度。

并发执行与并行执行

· 阅读需 2 分钟
素明诚
Full stack development

并发执行

在并发执行中,操作系统会通过时间分片技术在这两个程序之间快速切换。例如,它可能会让音乐播放器运行 10 毫秒,然后切换到文本编辑器运行 10 毫秒,如此反复。由于切换速度非常快,你会觉得音乐播放器和文本编辑器似乎是在同时运行。但不是真正的并行执行。

并行执行

如果你的电脑有两个或更多的 CPU 核心,那么它可以真正地并行执行这两个程序。例如,音乐播放器可能会在一个核心上运行,而文本编辑器会在另一个核心上运行。在这种情况下,两个程序确实是在同时运行,而不是通过轮转时间片交替运行。

对比

在只有一个 CPU 核心的系统中,通过并发执行,你仍然可以实现良好的系统响应性和多任务处理,但它不会提供真正的并行执行。在多核系统中,你可以获得真正的并行执行,从而提高系统的效率和性能。

stable-diffusion 插件推荐

· 阅读需 1 分钟
素明诚
Full stack development
标签:

Gradio 配合 FastAPI 搭建交互式Web应用与REST API服务

· 阅读需 1 分钟
素明诚
Full stack development

虚拟环境准备

进入项目文件夹,创建虚拟环境

python -m venv venv

激活环境

source venv/bin/activate  # 在Windows上使用 venv\Scripts\activate.bat

安装依赖

pip install gradio fastapi uvicorn

编写核心代码

在一个 Python 文件(例如core_logic.py)中编写你的核心逻辑。

def login(username, password):
if username == "admin" and password == "123123":
return "Login successful!"
else:
return "Login failed. Please check your credentials."

使用 gradio 创建页面

在另一个 Python 文件(例如gradio_app.py)中创建 Gradio 界面。

import gradio as gr
from core_logic import login

iface = gr.Interface(
fn=login,
inputs=["text", "password"],
outputs="text"
)

if __name__ == "__main__":
iface.launch()

创建 FastAPI 应用

在另一个 Python 文件(例如fastapi_app.py)中创建 FastAPI 应用

from fastapi import FastAPI
from pydantic import BaseModel
from core_logic import login

app = FastAPI()

class LoginData(BaseModel):
username: str
password: str

@app.post("/login/")
async def api_login(data: LoginData):
return {"message": login(data.username, data.password)}

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=7861)

运行应用

为 Gradio 界面运行gradio_app.py,并通过浏览器访问 Gradio 界面。

python gradio_app.py

为 FastAPI 应用运行fastapi_app.py,并通过浏览器或 API 客户端访问 FastAPI 提供的接口。

python fastapi_app.py

PS:这只是一个模板,如果想继续添加 UI 界面或者是接口需要你自己继续开发

Go 的数组为什么是值类型

· 阅读需 2 分钟
素明诚
Full stack development
	a := [3]int{1, 2, 3}
b := a // a 的值被复制到 b
b[0] = 42 // 修改 b 不会影响 a
fmt.Println(a) // 输出:[1 2 3]
fmt.Println(b) // 输出:[42 2 3]

性能

  • 这可能是一个重要因素。在栈上分配内存通常比在堆上分配和管理内存更快,特别是对于小数组。此外,值类型数组可以避免额外的垃圾收集开销。

语言简单性和一致性

  • Go 语言的设计者倾向于保持语言的简单性和一致性。将数组设计为值类型可以使语法更简单,更容易理解,特别是对于新手程序员。

明确的语义

  • 数组作为值类型提供了明确的语义,使得程序员可以很容易地理解代码的行为,特别是在赋值和参数传递时。

切片的引入

  • 通过引入切片作为引用类型,Go 语言为动态数组提供了一个强大而灵活的解决方案,同时保留了数组的简单性和效率。

数据安全

  • 值类型数组在多线程环境中提供了更好的数据安全性,因为它们不会被意外共享。

避免隐藏的性能陷阱

  • 在某些语言中,由于数组是引用类型,可能会引入隐藏的性能陷阱,例如意外的内存分配和垃圾收集。将数组作为值类型可以避免这些陷阱。

遗产和历史原因

  • Go 语言的设计可能受到其设计者以往经验和其他语言的影响,特别是 C 语言,其中数组也是值类型。

prototype 和 proto 的区别

· 阅读需 1 分钟
素明诚
Full stack development

prototype

  • prototype 是构造函数的一个属性,它是一个对象,包含了所有由该构造函数创建的对象实例应该共享的方法和属性。
  • 当你创建一个新的对象实例时(例如,通过 new 关键字),这个 prototype 对象就会成为新对象实例的原型。
function Person() {}
Person.prototype.greet = function() { console.log('Hello!'); };

const alice = new Person();
alice.greet(); // Output: Hello!

__proto__

  • __proto__ 是每个对象实例的一个属性,它指向创建该对象的构造函数的 prototype 对象。
  • 通过 __proto__,对象实例可以访问其原型上的方法和属性。
const alice = new Person();
console.log(alice.__proto__ === Person.prototype); // Output: true

简而言之:

  • prototype 是构造函数用来存储对象实例共享的方法和属性的地方。
  • __proto__ 是对象实例用来访问其原型(即构造函数的 prototype 对象)的方法和属性的链接。

这两者之间的关系是:

  • 对象实例的 __proto__ 属性指向创建它的构造函数的 prototype 对象。

使用现有的Gradio接口创建一个FastAPI应用

· 阅读需 1 分钟
素明诚
Full stack development

确保已经安装了

pip install fastapi gradio uvicorn

创建你的 Gradio 和 FastAPI 应用

import gradio as gr
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import uvicorn

# 定义你的模型功能
def model_function(input_text: str):
text_length = len(input_text)
return f"The text has {text_length} characters."

# 创建你的Gradio接口
gradio_iface = gr.Interface(fn=model_function, inputs="text", outputs="text")

# 创建你的FastAPI应用
app = FastAPI()

@app.get("/", response_class=HTMLResponse)
async def read_root():
# 返回 Gradio 界面的 HTML
return gradio_iface._get_html()

@app.get("/predict/")
def predict(input_text: str):
# 直接调用Gradio接口的模型功能
return {"result": gradio_iface.process({"text": input_text})["text"]}

# 如果是直接运行此脚本,启动Uvicorn服务器
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

现在,你可以运行此脚本,然后访问 http://localhost:8000 来查看和交互 Gradio 界面,或者访问 http://localhost:8000/docs 来查看和交互 FastAPI 自动生成的 Swagger 文档。

Go 语言中fmtPrintln 和 println 的区别

· 阅读需 2 分钟
素明诚
Full stack development

来源

  • fmt.Println 是 Go 语言标准库中 fmt 包的一部分。它是一个公共函数,可在任何 Go 程序中使用。
  • println 是一个内建函数,它不属于任何包,是 Go 语言低级操作的一部分。通常,println 主要用于调试目的。

功能性

  • fmt.Println 提供了丰富的功能。除了打印字符串,它还可以用来打印各种数据类型,并且能够处理格式化的字符串。此外,fmt.Println 在打印完字符串后会自动添加一个新行。
  • println 功能较为基础,它可以打印几乎所有值,但不支持格式化输出。它也会在输出后自动添加新行。

输出

  • fmt.Println 输出到标准输出(stdout),这意味着你可以很容易地重定向输出到其他位置,例如一个文件。
  • println 可能输出到标准错误输出(stderr),这取决于具体实现,且它的行为在不同的 Go 版本和平台上可能不同。

性能

  • fmt.Println 由于进行了更多的内部处理(例如接口转换和格式化),可能比 println 慢一些。
  • println 更快,因为它是一个简单的低级内建函数,没有额外的格式化开销。

使用场景

  • fmt.Println 是在生产环境中打印信息的推荐方式,因为它的行为明确且一致。
  • println 主要用于调试阶段,不推荐在生产代码中使用,因为它的实现在不同系统和 Go 的版本中可能会有所不同。

Java 受检Checked异常和非受检Unchecked异常

· 阅读需 2 分钟
素明诚
Full stack development

受检异常(Checked Exceptions)

  • 受检异常是那些在编译期间 Java 编译器要求必须处理(要么用 try-catch 捕获,要么在方法签名中用 throws 子句声明)的异常。
  • 它们继承自java.lang.Exception类,但不包括java.lang.RuntimeException类的子类。
  • 受检异常通常是由外部错误条件引起的,这些条件在程序运行时可能经常发生,程序员预见到这些异常情况并且被期望在编译阶段就处理它们。例如,试图根据指定的文件路径打开一个文件时可能会抛出FileNotFoundException,或者由于网络问题导致的IOException

非受检异常(Unchecked Exceptions)

  • 非受检异常是编译器不要求强制处理的异常。它们要么是由编程错误引起的(如访问 null 对象的成员、数组越界),要么是程序应该在运行时处理的异常。
  • 非受检异常包括java.lang.RuntimeException的所有子类和java.lang.Error的所有子类。RuntimeException是那些可能在 Java 虚拟机正常操作期间抛出的异常的超类。
  • 常见的RuntimeException包括NullPointerException(当应用试图在需要对象的地方使用 null 时,抛出此异常)、IndexOutOfBoundsException(指示某排序索引(如数组、字符串或向量)越界时抛出)等。
  • Error是指那些通常不被应用程序捕获的严重问题,例如OutOfMemoryErrorStackOverflowError等。

如何判断异常类型

  • 如果异常是 java.lang.RuntimeException 的子类,或者是 java.lang.Error,那它就是一个非受检异常。
  • 如果异常是 java.lang.Exception 的子类,但不是 java.lang.RuntimeException 的子类,那它就是一个受检异常