惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
About on SuperTechFans
Y
Y Combinator Blog
V
V2EX
Engineering at Meta
Engineering at Meta
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog
博客园 - 叶小钗
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
H
Help Net Security
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
WordPress大学
WordPress大学
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MongoDB | Blog
MongoDB | Blog
B
Blog
G
Google Developers Blog
J
Java Code Geeks
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
N
Netflix TechBlog - Medium
腾讯CDC

网上冲浪指南

成都双流区凤翔湖公园 四川雅安龙苍沟 Zig 中的类型替换 在 Zig 中实现 TaskCompletionSource 自定义 zig test runner 如何配置 ZigBrains 密码保护:借助图生图模型创建电子桌游地图 用 Cloudflare 给 WordPress 减负 接二连三修电脑 修复小新 Pad Pro 2021 蓝牙耳机没有声音的问题 手动升级一下 OpenWRT 如何安全地面向公网提供本地 NAS 上的 Web 服务 徒步·金堂开照寺二道坪山脊环线 Hello, ActivityPub 迁移博客到 VPS 优化博客网站的性能 解决 Qsirch 无法搜索文件夹的问题 N100 小主机遭遇 NVMe 硬盘故障:一次系统的诊断与反思 外接显示器 EDID 损坏如何处理 How to switch GitHub CLI account automatically
用 Zig 实现一个简单的任务队列
Zeeko · 2026-08-16 · via 网上冲浪指南

继人手一个个人博客引擎、Todo List、记帐软件后,现在新入行的开发者是不是应该打造一个自己的 Coding Agent 了?

发了这条动态后我就开始手搓自己的 Coding Agent 了,选择的编程语言是 Zig,目标是制作一个内存占用极小的 Coding Agent CLI 工具。

在实现工具调用的时候,我希望工具可以在一定程度上被并发调用,我选择的方案是任务队列。当 LLM 生成工具调用的时候,就入队一个工具调用请求,固定数量的 Consumers 会分别从各自的调用循环中取出队列中的一个工具调用任务,并在单独的线程中执行工具代码。

实现任务队列的难点在于我一开始带入了 JS/C# 的编程习惯,例如在 C# 中,我们可以很容易地想到利用 Queue 跟 TaskCompletionSource 实现让队列的 Consumer 来唤醒等待任务执行完成的 Producer。但是 Zig 中手动管理内存让这里的情况变得复杂了,因为 Producer 跟 Consumers 同时持有 TaskCompletionSource (假设存在这么一个 struct)的指针,并且都互相不知道其他引用方何时会退出,这使得 TaskCompletionSource 的内存释放时机的选择变得困难。

为了让内存上的管理变得直观,我选择将 TaskCompletionSource 放进一个 HashMap,然后 Producer 跟 Consumer 都通过一个 Id 查表来读写 TaskCompletionSource。

这就由引发了第二个问题,Zig 的 IoAllocator 一样是一个接口,目前 0.16 版本默认是基于线程的实现,这就意味着 Producer 跟 Consumer 有可能会在多个线程中竞争 HashMap 本身的数据。要解决这个问题就得进一步引入锁,还好 Zig 提供了 std.Io.RwLock。有了这些东西,就可以完成第一版任务队列了:

const Runner = @This();

const session = @import("../session.zig");

const std = @import("std");
const Io = std.Io;

const concurrent_task = 2;
queue: std.Io.Queue(TaskHandle),
queue_storage: [](TaskHandle),
io: std.Io,
task_group: std.Io.Group,
gpa: std.mem.Allocator,

task_id: std.atomic.Value(TaskHandle) = std.atomic.Value(TaskHandle).init(0),
tasks: TaskStore,

userdata: *anyopaque,
on_result: OnResultCallback,

const OnResultCallback = *const fn (*anyopaque, Task) void;
const TaskMap = std.AutoHashMapUnmanaged(TaskHandle, Task);

const TaskStore = struct {
    map: std.AutoHashMapUnmanaged(TaskHandle, Task) = .empty,
    lock: std.Io.RwLock = .init,
    io: std.Io,
    gpa: std.mem.Allocator,
    
    pub fn init(io: std.Io, gpa: std.mem.Allocator) TaskStore {
        return .{
            .io = io,
            .gpa = gpa,
        };
    }
    
    pub fn deinit(self: *TaskStore) void {
        self.map.deinit(self.gpa);
    }
    
    pub fn add(self: *TaskStore, task: Task) !void {
        self.lock.lockUncancelable(self.io);
        defer self.lock.unlock(self.io);
        try self.map.put(self.gpa, task.id, task);
    }
    
    pub fn get(self: *TaskStore, id: TaskHandle) *Task {
        return self.map.getPtr(id).?;
    }
    
    pub fn remove(self: *TaskStore, id: TaskHandle) void {
        self.lock.lockUncancelable(self.io);
        defer self.lock.unlock(self.io);
        _ = self.map.remove(id);
    }
};

const TaskHandle = u64;
fn nextTaskId(self: *Runner) u64 {
    return self.task_id.fetchAdd(1, .monotonic);
}

fn initTask(self: *Runner, tool_use: session.ToolUseStart) !TaskHandle {
    const task = Task.init(self.io, tool_use, self.nextTaskId());
    try self.tasks.add(task);
    return task.id;
}

const log = std.log.scoped(.tool_runner);
pub fn init(io: std.Io, gpa: std.mem.Allocator, userdata: *anyopaque, on_result: OnResultCallback) !Runner {
    const storage = try gpa.alloc(TaskHandle, concurrent_task * 10);
    return Runner{
        .io = io,
        .queue = .init(storage),
        .queue_storage = storage,
        .task_group = .init,
        .tasks = .init(io, gpa),
        .gpa = gpa,
        .userdata = userdata,
        .on_result = on_result,
    };
}

pub fn start(self: *Runner) !void {
    for (0..concurrent_task) |i| {
        try self.task_group.concurrent(self.io, consumer, .{self, i});
    }
}

pub fn stop(self: *Runner) void {
    self.task_group.cancel(self.io);
}

pub fn deinit(self: *Runner) void {
    self.queue.close(self.io);
    self.stop();
    self.gpa.free(self.queue_storage);
    self.tasks.deinit();
}

pub fn execute(self: *Runner, tool_call: session.ToolUseStart) !TaskHandle {
    const handle = try self.initTask(tool_call);
    log.info("Put task {d}", .{handle});
    try self.queue.putOne(self.io, handle);
    return handle;
}

pub fn interrupt(self: *Runner) void {
    self.task_group.cancel(self.io);
}

fn runTool(self: *Runner, handle: TaskHandle) void {
    var task = self.tasks.get(handle);

    const tc = task.tool_use;
    log.info("execute tool call: {s} {s}", .{ @tagName(tc.tool), tc.id });

    task.tool_use_result = .{
        .id = tc.id,
        .content = "",
        .timestamp = .{ .nanoseconds = 3 },
        .tool = tc.tool,
        .is_error = null,
    };
}

fn consumer(self: *Runner, num: usize) void {
    log.info("consumer started: {d}", .{num});
    while (self.queue.getOne(self.io)) |task_handle| {
        log.info("consumer {d} got task: {d}", .{num ,task_handle});
        var task = self.tasks.get(task_handle);
        if (task.canceled.load(.monotonic)) {
            self.tasks.remove(task.id);
            log.info("Consumer {d} skips canceled task {d}", .{num ,task.id});
            continue;
        }
        task.group.async(self.io, runTool, .{ self, task_handle });
        task.group.await(self.io) catch |err| {
            var t = self.tasks.get(task_handle);
            t.err = err;
            log.info("Consumer {d} got error {s} for task {d}", .{num ,@errorName(err), task.id});
            self.on_result(self.userdata, t.*);
            continue;
        };
        const t = self.tasks.get(task_handle);
        log.info("Consumer {d} got result for task {d}", .{num ,t.id});
        self.on_result(self.userdata, t.*);
    } else |_| {
        log.info("Consumer {d} stopped", .{num});
    }
}

const Task = struct {
    tool_use: session.ToolUseStart,
    tool_use_result: ?session.ToolUseCompleted = null,
    group: Io.Group,
    io: Io,
    canceled: std.atomic.Value(bool) = .init(false),
    id: TaskHandle,
    err: ?anyerror = null,

    fn init(io: Io, tool_use: session.ToolUseStart, id: TaskHandle) Task {
        return .{ .io = io, .tool_use = tool_use, .group = .init, .id = id };
    }

    fn deinit(self: *Task) void {
        self.cancel();
    }

    fn await(self: *Task) !session.ToolUseCompleted {
        try self.group.await(self.io);
        return self.tool_use_result orelse error.Canceled;
    }

    fn cancel(self: *Task) void {
        self.canceled.store(true, .monotonic);
        self.group.cancel(self.io);
    }

    pub fn jsonStringify(self: *const Task, jws: anytype) std.json.Stringify.Error!void {
        try jws.write(.{
            .id = self.id,
            .err = self.err,
            .tool_use = self.tool_use,
            .tool_use_result = self.tool_use_result,
        });
    }
};