go java python dart 内存模型区别

python函数入参列表list,列表list被修改了;

 

go传入数组,传入的是副本,原数组没有被修改; 开销: 高(拷贝整个数组)

go传入切片,传入的是副本,原切片被修改;开销: 低(拷贝24字节)  (切片的底层数组共享)

 

package main

import "fmt"

func f(a [3]int) {
a[1] = 123
}
func g(s []int){
s[1]=123
}

func main() {
arr := [3]int{1, 2, 3}
fmt.Println(arr)
f(arr)
fmt.Println(arr)

var slice1 []int
slice1 = []int{1, 2, 3}
fmt.Println(slice1)
g(slice1)
fmt.Println(slice1)
}

[1 2 3]
[1 2 3]
[1 2 3]
[1 123 3]

 

python

a=[1,2,3]
def f(b):
b[1]=123

print(a)
f(a)
print(a)


[1, 2, 3]
[1, 123, 3]

 

数组内存分布

var arr [5]int{1, 2, 3, 4, 5}

内存地址: 0x100 0x108 0x110 0x118 0x120
┌──────┬──────┬──────┬──────┬──────┐
│ 1 │ 2 │ 3 │ 4 │ 5 │
└──────┴──────┴──────┴──────┴──────┘

大小: 5 * 8 = 40 bytes (int64)

 

 

切片内存分布

slice := []int{1, 2, 3, 4, 5}

Slice Header (24 bytes on 64-bit):
┌─────────────────┐
│ array: 0x100 │ ← 指针(8 bytes)
├─────────────────┤
│ len: 5 │ ← 长度(8 bytes)
├─────────────────┤
│ cap: 5 │ ← 容量(8 bytes)
└─────────────────┘
↓ 指向
底层数组:
┌──────┬──────┬──────┬──────┬──────┐
│ 1 │ 2 │ 3 │ 4 │ 5 │
└──────┴──────┴──────┴──────┴──────┘

 

 

特性 GoJava (JVM)PythonDart
内存管理 自动 GC(三色标记法) 自动 GC(分代收集) 引用计数 + 循环垃圾检测 自动 GC(分代收集)
并发模型 CSP 模型(通过 channel 通信) 共享内存(线程+锁) GIL 锁(同一时刻单线程) Isolate 隔离(不共享内存)
内存安全 编译期逃逸分析 强类型,基本只在堆分配 动态类型,全对象化 严格类型,AOT/JIT 混合

 

Go: 轻量级与通信
Go 的设计哲学是“不要通过共享内存来通信,而要通过通信来共享内存”。 
  • 逃逸分析:编译器会自动决定变量分配在栈(Stack)还是堆(Heap)上。如果变量在函数结束后不再使用,优先放在栈上,这极大降低了 GC 压力。
  • 并发模型:利用 Goroutine,通过 channel 传递数据,避免了传统多线程中复杂的锁竞争问题。
 
Java: 经典的共享模型
Java 内存模型(JMM)围绕多线程共享堆内存展开。 
  • 分代收集:将内存分为新生代、老年代,针对不同寿命的对象使用不同的 GC 算法。
  • 内存屏障:通过 volatile 等关键字确保多线程下的可见性和有序性,依赖锁机制(synchronized/Lock)来保证线程安全。 
 
Python: 解释器的限制
Python 的内存管理深受 GIL(全局解释器锁) 的影响。 
  • 引用计数:对象被引用一次计数加 1,归零即销毁。这是最直接的内存释放方式。
  • 并发限制:由于 GIL 的存在,多个 CPU 核心无法同时执行 Python 字节码,因此 Python 主要是通过多进程或 asyncio 来处理并发,而非共享内存的多线程。 
 
Dart: 隔离的 Isolate
Dart 在 Flutter 等场景下的内存表现优异,其核心在于 Isolate。 
  • 无共享内存:每个 Isolate 拥有自己独立的堆和栈,不同 Isolate 之间不能直接访问彼此内存,只能通过消息传递通信。这种设计彻底消除了加锁带来的开销和死锁风险。
  • 分代假说:针对 UI 框架中对象“瞬时创建、瞬时销毁”的特点,Dart 拥有极其高效的新生代清理机制。 

 

 

 


Python 进程
├─ Main Thread (主线程)
├─ Thread 1 (子线程)
├─ Thread 2 (子线程)
└─ ...

所有线程共享:
- 同一进程内存空间
- 全局变量
- 堆内存
- 文件描述符

每个线程独立:
- 栈空间
- 寄存器状态
- 程序计数器

# GIL 保证同一时刻只有一个线程执行 Python 字节码
# 这是 CPython 的设计决策,简化了内存管理

执行流程:
Thread 1: [获取 GIL] → [执行 100 个字节码] → [释放 GIL] → [等待]
Thread 2: [等待] → [获取 GIL] → [执行 100 个字节码] → [释放 GIL] → [等待]
Thread 1: [等待] → [获取 GIL] → ...



Python 有 GIL(全局解释器锁)限制,同一时刻只有一个线程执行字节码。而 Java 没有 GIL 限制,可以实现真正的并行执行。
Python: Thread 1 [获取GIL] → 执行 → [释放GIL] → Thread 2 [等待] → [获取GIL] → ...
Java: Thread 1 ████████ 真正并行执行
Thread 2 ████████ 真正并行执行
Thread 3 ████████ 真正并行执行




 

        M:N 模型(Goroutine : Machine : Processor)

G (Goroutine) → M (OS Thread) → P (Processor) → CPU Core

GMP 调度模型:
┌─────────────────────────────────┐
│ G1 G2 G3 (Goroutines) │ ← 用户态,轻量级
└──────┬──┬──┬───────────────────┘
│ │ │
┌──────▼──▼──▼───────────────────┐
│ M1 M2 (OS Threads) │ ← 内核线程
└──────┬──┬──────────────────────┘
│ │
┌──────▼──▼──────────────────────┐
│ P1 P2 (Processors) │ ← 逻辑处理器
└──────┬──┬──────────────────────┘
│ │
▼ ▼
CPU1 CPU2

 

 


Java Thread
Go Goroutine
创建时间
~1ms
~2μs
内存占用
1MB
2KB (动态增长)
切换成本
~1-10μs
~200ns
最大数量
~几千
~百万
调度方式
抢占式(OS)
协作式 + 抢占式

 

特性
Java
Go
对象分配
优先在 Eden 区
逃逸分析决定栈或堆
栈管理
固定大小(可配置)
动态增长(2KB 起步)
线程栈
1MB 默认
2KB 默认(goroutine)
内存区域
复杂(新生代、老年代等)
简单(堆 + 栈)

 

 

信号量

场景 2:API 速率限制

import threading
import time
import requests

class RateLimiter:
"""API 速率限制器"""
def __init__(self, max_requests=10, time_window=60):
self.semaphore = threading.Semaphore(max_requests)
self.time_window = time_window

def make_request(self, url):
"""发送 API 请求"""
self.semaphore.acquire()
try:
print(f"发送请求到: {url}")
response = requests.get(url, timeout=5)
return response
finally:
# 在时间窗口后释放信号量
threading.Timer(self.time_window, self.semaphore.release).start()

# 限制每秒最多 5 个请求
limiter = RateLimiter(max_requests=5, time_window=1)

def fetch_data(item_id):
url = f"https://api.example.com/items/{item_id}"
response = limiter.make_request(url)
print(f"获取到数据: {item_id}")

threads = [threading.Thread(target=fetch_data, args=(i,)) for i in range(20)]
for t in threads:
t.start()




代码优化



这段代码在逻辑思路上是可行的,但在高并发或严苛的生产环境下存在几个隐患。它能起到“限流”的作用,但并不是一个标准的“令牌桶”或“漏桶”算法实现。
以下是详细分析:

1. 核心优点

 
  • 非阻塞式释放:使用 threading.Timer 在后台异步释放信号量,不会阻塞当前请求线程,设计很巧妙。
  • 基础限流有效:对于低频率、少量的并发请求,它能强制维持“每 time_window 秒最多 max_requests 个请求”的限制。

2. 潜在隐患(Bug 与性能)

 
  • 线程爆炸风险:
    每次请求都会创建一个新的 threading.Timer 线程。如果你要在 1 分钟内处理 1000 个请求,就会额外创建 1000 个瞬时线程来负责 release。这会显著消耗系统资源,甚至触发操作系统的线程数限制。
  • 误差累积:
    Python 的 Timer 并不保证精确的纳秒级触发。在高负载下,Timer 的启动和执行会有延迟,导致实际的限流频率比预设的要慢。
  • 信号量计数值溢出:
    由于使用的是 Semaphore 而非 BoundedSemaphore,如果代码逻辑在某些异常情况下多次触发了释放逻辑,信号量的计数器可能会超过初始值,导致限流失效。
  • 异常处理不足:
    如果 requests.get 抛出异常(如超时或网络错误),finally 块依然会启动 Timer。这本身没错,但要注意 API 报错时是否仍计入速率限制。

3. 改进建议

如果你追求代码简洁且并发量不大,可以改用更轻量的方式。如果并发量大,建议使用专门的限流库。
轻量级改进版本(减少线程消耗):
import time
from collections import deque

class RateLimiter:
    def __init__(self, max_requests=5, time_window=1):
        self.max_requests = max_requests
        self.time_window = time_window
        self.requests_times = deque() # 记录请求时间戳
        self.lock = threading.Lock()

    def make_request(self, url):
        with self.lock:
            current_time = time.time()
            # 移除时间窗口之外的旧记录
            while self.requests_times and self.requests_times[0] <= current_time - self.time_window:
                self.requests_times.popleft()
            
            # 如果达到上限,则等待(或者直接报错/重试)
            if len(self.requests_times) >= self.max_requests:
                sleep_time = self.requests_times[0] + self.time_window - current_time
                time.sleep(max(0, sleep_time))
            
            self.requests_times.append(time.time())
            
        # 执行真正的请求
        print(f"发送请求: {url}")
        return requests.get(url)

总结

你的代码作为学习信号量的高级用法非常棒,但在生产环境中:
 
  1. 如果请求量大,建议使用 ratelimittenacity 等成熟库。
  2. 如果是分布式环境,必须使用 Redis 实现信号量或令牌桶。
 
 

主要问题
Timer 线程泄漏:每次请求都创建新 Timer,20 个请求会创建 20 个后台线程
信号量释放时机不准确:Timer 是异步的,可能导致并发控制不精确
缺少错误处理:网络请求失败时没有重试机制
无法优雅关闭:没有清理资源的方法
时间窗口实现不合理:应该用滑动窗口而非固定延迟

 import threading
import time
import requests
from collections import deque

class RateLimiter:
"""API 速率限制器 - 基于滑动窗口算法"""

def __init__(self, max_requests=10, time_window=60):
self.max_requests = max_requests
self.time_window = time_window
self.semaphore = threading.Semaphore(max_requests)
self.request_times = deque()
self.lock = threading.Lock()
self._shutdown = False

def _clean_old_requests(self):
"""清理超出时间窗口的请求记录"""
now = time.time()
cutoff = now - self.time_window
while self.request_times and self.request_times[0] < cutoff:
self.request_times.popleft()

def _wait_for_slot(self):
"""等待可用的请求槽位"""
while not self._shutdown:
with self.lock:
self._clean_old_requests()
if len(self.request_times) < self.max_requests:
self.request_times.append(time.time())
return True

# 计算需要等待的时间
with self.lock:
if self.request_times:
oldest = self.request_times[0]
wait_time = self.time_window - (time.time() - oldest) + 0.1
else:
wait_time = 0.1

if wait_time > 0:
time.sleep(min(wait_time, 1.0))

raise RuntimeError("RateLimiter has been shut down")

def make_request(self, url, max_retries=3, timeout=5):
"""发送 API 请求,带重试机制"""
self._wait_for_slot()

for attempt in range(max_retries):
try:
print(f"[尝试 {attempt + 1}/{max_retries}] 发送请求到: {url}")
response = requests.get(url, timeout=timeout)
response.raise_for_status()

print(f"✓ 请求成功: {url} (状态码: {response.status_code})")
return response

except requests.exceptions.Timeout:
print(f"⚠ 请求超时: {url}")
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)

except requests.exceptions.ConnectionError as e:
print(f"⚠ 连接错误: {url} - {e}")
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)

except requests.exceptions.HTTPError as e:
print(f"✗ HTTP 错误: {url} - {e}")
raise

return None

def shutdown(self):
"""优雅关闭速率限制器"""
self._shutdown = True
print("RateLimiter 已关闭")

# 限制每秒最多 5 个请求
limiter = RateLimiter(max_requests=5, time_window=1)

def fetch_data(item_id):
"""获取数据的 worker 函数"""
try:
url = f"https://api.example.com/items/{item_id}"
response = limiter.make_request(url)
if response:
print(f"✅ 获取到数据: {item_id}, 大小: {len(response.content)} bytes")
except Exception as e:
print(f"❌ 获取数据失败 {item_id}: {e}")

try:
threads = [threading.Thread(target=fetch_data, args=(i,), daemon=True)
for i in range(20)]

for t in threads:
t.start()

for t in threads:
t.join(timeout=30)

print("\n所有请求完成")

finally:
limiter.shutdown()

 


1. 滑动窗口算法替代 Timer

# ❌ 原代码:创建大量 Timer 线程
threading.Timer(self.time_window, self.semaphore.release).start()

# ✅ 优化后:使用 deque 记录请求时间,自动清理过期记录
self.request_times = deque()
self._clean_old_requests() # 移除超出时间窗口的记录


优势:
不会创建额外线程,避免资源泄漏
更精确的速率控制
支持动态调整时间窗口

2. 指数退避重试机制

for attempt in range(max_retries):
try:
response = requests.get(url, timeout=timeout)
response.raise_for_status()
return response
except requests.exceptions.Timeout:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # 1s, 2s, 4s...

3. 优雅关闭支持
def shutdown(self):
self._shutdown = True

# 使用时
try:
# ... 执行任务 ...
finally:
limiter.shutdown() # 确保资源清理

4. 守护线程 + 超时控制
threads = [threading.Thread(target=fetch_data, args=(i,), daemon=True)
for i in range(20)]

for t in threads:
t.join(timeout=30) # 最多等待 30 秒

更高级的版本(使用令牌桶算法)

import threading
import time
import requests

class TokenBucketRateLimiter:
"""基于令牌桶算法的速率限制器"""

def __init__(self, rate=5, capacity=10):
"""
:param rate: 每秒生成的令牌数
:param capacity: 桶的最大容量
"""
self.rate = rate
self.capacity = capacity
self.tokens = capacity
self.last_refill = time.time()
self.lock = threading.Lock()
self._shutdown = False

def _refill(self):
"""补充令牌"""
now = time.time()
elapsed = now - self.last_refill
new_tokens = elapsed * self.rate
self.tokens = min(self.capacity, self.tokens + new_tokens)
self.last_refill = now

def acquire(self, timeout=None):
"""获取令牌"""
deadline = time.time() + timeout if timeout else None

while not self._shutdown:
with self.lock:
self._refill()
if self.tokens >= 1:
self.tokens -= 1
return True

if deadline and time.time() > deadline:
return False

time.sleep(0.1)

raise RuntimeError("TokenBucketRateLimiter has been shut down")

def make_request(self, url, timeout=5, max_retries=3):
"""发送请求"""
if not self.acquire(timeout=10):
raise TimeoutError("等待令牌超时")

for attempt in range(max_retries):
try:
response = requests.get(url, timeout=timeout)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(min(2 ** attempt, 10))

def shutdown(self):
self._shutdown = True

# 使用:每秒 5 个请求,峰值可达 10 个
limiter = TokenBucketRateLimiter(rate=5, capacity=10)

def fetch_data(item_id):
try:
url = f"https://api.example.com/items/{item_id}"
response = limiter.make_request(url)
print(f"✅ {item_id}: {len(response.content)} bytes")
except Exception as e:
print(f"❌ {item_id}: {e}")

threads = [threading.Thread(target=fetch_data, args=(i,), daemon=True)
for i in range(20)]

try:
for t in threads:
t.start()
for t in threads:
t.join(timeout=30)
finally:
limiter.shutdown()

image

 

 

 

 

 

 

 

 

 

 

 

 
 
 

 

posted @ 2026-04-09 22:02  papering  阅读(4)  评论(0)    收藏  举报