Worker 线程中的 函数序列化 模式

工作者线程也可以利用函数序列化来初始化行内脚本。

因为函数的toString()方法会返回函数代码的字符串,而函数可以在父上下文中定义但在子上下文中执行。


什么是函数序列化?

函数序列化是将函数(包括其代码、闭包环境等信息)转换为可以存储或传输的格式(通常是字节流或字符串),以便后续可以反序列化并重新执行的过程。


主要特点:

序列化的内容

  • 函数源代码/字节码

  • 函数名称和参数

  • 闭包捕获的变量值

  • 依赖的模块信息


JavaScript 的实现

javascript 复制代码
// 通过toString()获取函数源码
const func = (x) => x * 2;
const serialized = func.toString();
// 反序列化
const restoredFunc = eval('(' + serialized + ')');

总结

函数序列化是分布式系统和持久化缓存中的重要技术,但需要特别注意安全性环境一致性

在实际应用中,通常推荐使用成熟的分布式计算框架(如Spark、Dask)或云服务提供的函数计算服务,而不是手动处理函数序列化。


Worker线程中的函数序列化模式

1. 基本工作原理

javascript

javascript 复制代码
// 主线程(父上下文)
function complexCalculation(data) {
    // 使用父上下文中的依赖
    const config = { multiplier: 2 };
    
    // 复杂计算逻辑
    return data.map(item => item * config.multiplier);
}

// 将函数转换为字符串(序列化)
const functionCode = complexCalculation.toString();

// 创建Worker并传递函数代码
const worker = new Worker('worker.js');

// 通过消息传递函数代码和数据
worker.postMessage({
    functionCode: functionCode,
    data: [1, 2, 3, 4, 5]
});

2. Worker端实现

javascript

javascript 复制代码
// worker.js - 子上下文
self.onmessage = function(e) {
    const { functionCode, data } = e.data;
    
    // 在Worker上下文中重建函数(反序列化)
    const func = new Function('data', `
        // 添加函数体
        ${functionCode.match(/\{([\s\S]*)\}$/)[1]}
    `);
    
    // 执行函数
    const result = func(data);
    
    // 返回结果
    self.postMessage(result);
};

3. 更完整的闭包捕获方案

javascript

javascript 复制代码
// 主线程:捕获闭包变量并序列化
function createWorkerTask() {
    const secretKey = 'worker-' + Math.random();
    const multiplier = 3;
    
    function processData(data) {
        // 这个函数引用了外部作用域的变量
        console.log(`Using key: ${secretKey}`);
        return data.map(x => x * multiplier);
    }
    
    // 捕获闭包环境
    const closureVars = { secretKey, multiplier };
    const functionBody = processData.toString();
    
    return {
        functionBody,
        closureVars,
        data: [1, 2, 3]
    };
}

// Worker中重建闭包
self.onmessage = async function(e) {
    const { functionBody, closureVars, data } = e.data;
    
    // 在Worker中重新创建闭包环境
    const funcFactory = new Function(
        'closureVars',
        `
        // 将闭包变量注入作用域
        const { secretKey, multiplier } = closureVars;
        
        // 定义处理函数
        const processData = ${functionBody};
        
        return processData;
        `
    );
    
    const processData = funcFactory(closureVars);
    const result = processData(data);
    self.postMessage(result);
};

4. 模块依赖的处理

javascript

javascript 复制代码
// 支持依赖模块的函数序列化
class WorkerFunctionSerializer {
    static serialize(func, dependencies = {}) {
        return {
            code: func.toString(),
            dependencies: Object.keys(dependencies).reduce((acc, key) => {
                acc[key] = dependencies[key].toString();
                return acc;
            }, {}),
            imports: this.extractImports(func.toString())
        };
    }
    
    static extractImports(code) {
        // 提取import/require语句
        const importRegex = /(?:import|require)\([^)]+\)|import[^'"]+from[^'"]+/g;
        return code.match(importRegex) || [];
    }
}

// 使用示例
const mathUtils = {
    square: (x) => x * x,
    cube: (x) => x * x * x
};

function complexMath(data) {
    return data.map(item => mathUtils.square(item) + mathUtils.cube(item));
}

const serialized = WorkerFunctionSerializer.serialize(complexMath, { mathUtils });

5. 实用的Worker池实现

javascript

javascript 复制代码
class FunctionWorkerPool {
    constructor(poolSize = navigator.hardwareConcurrency || 4) {
        this.pool = [];
        this.queue = [];
        
        for (let i = 0; i < poolSize; i++) {
            this.createWorker();
        }
    }
    
    createWorker() {
        const workerCode = `
            self.onmessage = async function(e) {
                const { functionCode, data, taskId } = e.data;
                
                try {
                    // 安全地执行函数
                    const func = (new Function('return ' + functionCode))();
                    const result = func(data);
                    
                    self.postMessage({
                        taskId,
                        result,
                        success: true
                    });
                } catch (error) {
                    self.postMessage({
                        taskId,
                        error: error.message,
                        success: false
                    });
                }
            };
        `;
        
        const blob = new Blob([workerCode], { type: 'application/javascript' });
        const worker = new Worker(URL.createObjectURL(blob));
        this.pool.push(worker);
    }
    
    execute(func, data) {
        return new Promise((resolve, reject) => {
            const taskId = Date.now() + Math.random();
            
            const task = {
                taskId,
                func,
                data,
                resolve,
                reject
            };
            
            this.queue.push(task);
            this.processQueue();
        });
    }
    
    processQueue() {
        const availableWorker = this.pool.find(w => !w.busy);
        if (!availableWorker || this.queue.length === 0) return;
        
        const task = this.queue.shift();
        availableWorker.busy = true;
        
        availableWorker.onmessage = (e) => {
            availableWorker.busy = false;
            
            if (e.data.success) {
                task.resolve(e.data.result);
            } else {
                task.reject(new Error(e.data.error));
            }
            
            this.processQueue();
        };
        
        availableWorker.postMessage({
            functionCode: task.func.toString(),
            data: task.data,
            taskId: task.taskId
        });
    }
}

// 使用示例
const pool = new FunctionWorkerPool(4);

const results = await Promise.all([
    pool.execute((data) => data.map(x => x * 2), [1, 2, 3]),
    pool.execute((data) => data.reduce((a, b) => a + b, 0), [1, 2, 3, 4])
]);

6. 安全考虑和限制

javascript

javascript 复制代码
// 1. 使用沙箱执行
class SafeWorkerExecutor {
    static executeInWorker(func, data) {
        // 创建隔离的Worker环境
        const workerCode = `
            // 白名单允许的函数和API
            const ALLOWED_APIS = ['Math', 'JSON', 'Array', 'Object', 'Number'];
            
            // 创建安全的执行上下文
            const createSafeContext = () => {
                const context = {};
                ALLOWED_APIS.forEach(api => {
                    context[api] = self[api];
                });
                return context;
            };
            
            self.onmessage = function(e) {
                const { functionCode, data } = e.data;
                const safeContext = createSafeContext();
                
                // 使用with语句限制作用域
                try {
                    const func = new Function(
                        'data', 
                        'with(this) { return (' + functionCode + ')(data); }'
                    ).bind(safeContext);
                    
                    const result = func(data);
                    self.postMessage({ success: true, result });
                } catch (error) {
                    self.postMessage({ success: false, error: error.message });
                }
            };
        `;
        
        // 创建并执行Worker...
    }
}

// 2. 处理闭包变量的替代方案
function serializeWithClosures(func) {
    // 提取函数体
    const body = func.toString();
    
    // 分析依赖的外部变量
    const closureRefs = this.analyzeClosureReferences(body);
    
    // 返回重建所需的所有信息
    return {
        body,
        closureRefs,
        // 或者使用Proxy捕获getter
        capture: new Proxy({}, {
            get(target, prop) {
                return Reflect.get(globalThis, prop);
            }
        })
    };
}

关键优势

  1. 代码传输:可以动态生成和执行代码

  2. 环境隔离:Worker提供天然的沙箱环境

  3. 并行计算:利用多核CPU能力

  4. 灵活部署:无需预编译或预加载


注意事项

  • 安全风险:避免执行不受信任的代码

  • 性能开销:序列化/反序列化有成本

  • 闭包限制:只能捕获可序列化的变量值

  • 浏览器兼容性:Blob URL在不同浏览器中的行为可能不同


这种模式在Web Workers、Service Workers和某些Node.js集群场景中非常有用!


javascript 复制代码
//斐波那契数列
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}
//工作者线程也可以利用函数序列化来初始化行内脚本。
//因为函数的toString()方法会返回函数代码的字符串,而函数可以在父上下文中定义但在子上下文中执行。
const workerScript = `self.postMessage = ((${fibonacci.toString()})(9));`;
const worker = new Worker(URL.createObjectURL(new Blob([workerScript])));
worker.onmessage = function (e) {
  console.log(e.data);
};

你的这段代码是一个巧妙而简洁的Worker内联函数序列化示例!让我详细分析并扩展这个思路:


代码分析

1. 工作原理详解

javascript

javascript 复制代码
// 原始函数
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// 转换为字符串:fibonacci.toString() 返回 "function fibonacci(n) {...}"
// 拼接成Worker脚本:
const workerScript = `self.postMessage = ((${fibonacci.toString()})(9));`;

// 等价于:
// const workerScript = `
//   self.postMessage = ((
//     function fibonacci(n) {
//       if (n <= 1) return n;
//       return fibonacci(n - 1) + fibonacci(n - 2);
//     }
//   )(9));
// `;

// 创建Blob URL
const blob = new Blob([workerScript], { type: 'application/javascript' });
const blobURL = URL.createObjectURL(blob);

// 创建Worker
const worker = new Worker(blobURL);

2. 执行流程

  1. fibonacci.toString() 将函数序列化为字符串

  2. 拼接成完整的Worker脚本代码

  3. 通过Blob URL创建临时Worker脚本文件

  4. Worker立即执行函数并调用 self.postMessage

  5. 主线程通过 onmessage 接收结果

3. 更实用的变体

javascript

javascript 复制代码
// 1. 传递参数和函数
function createWorkerTask(func, ...args) {
    const script = `
        self.onmessage = function(e) {
            const result = (${func.toString()})(...e.data.args);
            self.postMessage(result);
        };
    `;
    
    const worker = new Worker(URL.createObjectURL(new Blob([script])));
    
    return {
        execute: function(...workerArgs) {
            return new Promise((resolve) => {
                worker.onmessage = (e) => resolve(e.data);
                worker.postMessage({ args: workerArgs });
            });
        },
        terminate: () => worker.terminate()
    };
}

// 使用
const fibonacci = (n) => n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
const task = createWorkerTask(fibonacci);
task.execute(10).then(result => console.log(result)); // 55

4. 支持闭包和依赖

javascript

javascript 复制代码
// 创建支持闭包的Worker
function createClosureWorker(mainFunc, dependencies = {}) {
    const depCode = Object.entries(dependencies)
        .map(([name, func]) => `const ${name} = ${func.toString()};`)
        .join('\n');
    
    const script = `
        ${depCode}
        
        const mainFunction = ${mainFunc.toString()};
        
        self.onmessage = function(e) {
            const result = mainFunction(e.data);
            self.postMessage(result);
        };
    `;
    
    return new Worker(URL.createObjectURL(new Blob([script])));
}

// 示例:带有依赖的函数
const mathHelpers = {
    square: x => x * x,
    cube: x => x * x * x
};

function calculate(x) {
    return mathHelpers.square(x) + mathHelpers.cube(x);
}

const worker = createClosureWorker(calculate, mathHelpers);
worker.postMessage(5);
worker.onmessage = e => console.log(e.data); // 150 (25 + 125)

上述代码纠错

Uncaught ReferenceError: mathHelpers is not defined

at calculate (230d3d45-7161-4ae2-b017-87d7c7538274:6:9)

at self.onmessage (230d3d45-7161-4ae2-b017-87d7c7538274:10:28)


看到控制台报错,我在询问通义灵码的时候,最开始只输入了

复制代码
Uncaught ReferenceError: mathHelpers is not defined

我以为后面两行信息没有意义,实际大有意义。


通义灵码的纠错

实际上,经过仔细检查,我发现原始代码应该是正确的。mathHelpers已经在主上下文中定义,并且作为依赖传递给createClosureWorker函数。错误可能是由于以下原因之一:

  1. 你可能在某个地方直接调用了calculate函数而没有确保mathHelpers在当前作用域中可用。
  2. 可能是在worker执行过程中出现了错误。

**为了进一步确认问题,请提供完整的错误堆栈信息,**这样我可以更准确地定位问题所在。同时,你可以检查控制台是否有其他相关的错误信息。


注意:完整的错误信息能帮助AI更准确的分析错误所在。


根据错误信息,问题是出现在 Web Worker 内部执行 calculate 函数时找不到 mathHelpers 对象。从错误堆栈可以看出,错误发生在 worker 环境中。

让我分析一下问题所在。在 createClosureWorker 函数中,虽然尝试将 mathHelpers 作为依赖传递,但是生成的 worker 脚本中可能存在一些问题。


问题出在 mathHelpers 对象的定义方式上。原来的**箭头函数在序列化后可能在 worker 环境中无法正确重建。**我已经将其改为传统的函数表达式形式。

javascript 复制代码
 const mathHelpers = {
    square: function(x) { return x * x; },
    cube: function(x) { return x * x * x; }
};

另外,还有一种更好的解决方案是重构 createClosureWorker 函数,使其正确处理不同类型的依赖项(包括对象):

javascript 复制代码
// ... existing code ...
      //支持闭包和依赖
      // 创建支持闭包的Worker
      function createClosureWorker(mainFunc, dependencies = {}) {
        // 处理函数和对象依赖
        const depCode = Object.entries(dependencies)
          .map(([name, value]) => {
            if (typeof value === 'function') {
              return `const ${name} = ${value.toString()};`;
            } else if (typeof value === 'object' && value !== null) {
              return `const ${name} = ${JSON.stringify(value)};`;
            } else {
              return `const ${name} = ${JSON.stringify(value)};`;
            }
          })
          .join("\n");

        const script = `
        ${depCode}
        
        const mainFunction = ${mainFunc.toString()};
        
        self.onmessage = function(e) {
            const result = mainFunction(e.data);
            self.postMessage(result);
        };
    `;

        return new Worker(URL.createObjectURL(new Blob([script])));
      }

      // 示例:带有依赖的函数
      const mathHelpers = {
        square: function(x) { return x * x; },
        cube: function(x) { return x * x * x; }
      };

      function calculate(x) {
        return mathHelpers.square(x) + mathHelpers.cube(x);
      }

      const worker4 = createClosureWorker(calculate, mathHelpers);
      worker4.postMessage(5);
      worker4.onmessage = (e) => console.log(e.data); // 150 (25 + 125)
// ... existing code ...

这个改进版本的 createClosureWorker 函数能够正确处理不同类型的依赖项:

  1. 对于函数类型,使用 toString() 方法序列化
  2. 对于对象类型,使用 JSON.stringify() 序列化
  3. 对于其他基本类型,也使用 JSON.stringify() 序列化

这样可以确保在 worker 环境中正确重建所有依赖项,包括 mathHelpers 对象,从而解决 "mathHelpers is not defined" 的错误。


实际的错误原因

mathHelpers 在序列化的过程中丢失name

javascript 复制代码
Generated worker script: 
          const square = function (x) {
          return x * x;
        };
const cube = function (x) {
          return x * x * x;
        };

          const mainFunction = function calculate(x) {
        return square(x) + cube(x);
      };

          self.onmessage = function(e) {
              const result = mainFunction(e.data);
              self.postMessage(result);
          };

修改调用即可

javascript 复制代码
// function calculate(x) {
//   return mathHelpers.square(x) + mathHelpers.cube(x);
// }

//mathHelpers 序列化后丢失 name
function calculate(x) {
  return square(x) + cube(x);
}

5. 批量处理版本

javascript

javascript 复制代码
class FunctionWorker {
    constructor(func) {
        this.func = func;
        this.initWorker();
    }
    
    initWorker() {
        const script = `
            let currentFunc = null;
            
            self.onmessage = async function(e) {
                if (e.data.type === 'SET_FUNCTION') {
                    // 动态设置新函数
                    currentFunc = eval('(' + e.data.funcCode + ')');
                    self.postMessage({ type: 'READY' });
                } 
                else if (e.data.type === 'EXECUTE') {
                    try {
                        const result = currentFunc(...e.data.args);
                        self.postMessage({
                            type: 'RESULT',
                            id: e.data.id,
                            result
                        });
                    } catch (error) {
                        self.postMessage({
                            type: 'ERROR',
                            id: e.data.id,
                            error: error.message
                        });
                    }
                }
            };
        `;
        
        this.worker = new Worker(URL.createObjectURL(new Blob([script])));
        this.callbacks = new Map();
        this.nextId = 0;
        
        this.worker.onmessage = (e) => {
            if (e.data.type === 'RESULT') {
                const callback = this.callbacks.get(e.data.id);
                if (callback) {
                    callback.resolve(e.data.result);
                    this.callbacks.delete(e.data.id);
                }
            }
        };
        
        // 设置初始函数
        this.worker.postMessage({
            type: 'SET_FUNCTION',
            funcCode: this.func.toString()
        });
    }
    
    execute(...args) {
        return new Promise((resolve, reject) => {
            const id = this.nextId++;
            
            this.callbacks.set(id, { resolve, reject });
            
            this.worker.postMessage({
                type: 'EXECUTE',
                id,
                args
            });
        });
    }
    
    updateFunction(newFunc) {
        this.func = newFunc;
        this.worker.postMessage({
            type: 'SET_FUNCTION',
            funcCode: newFunc.toString()
        });
    }
    
    terminate() {
        this.worker.terminate();
    }
}

// 使用
const worker = new FunctionWorker((a, b) => a * b);
worker.execute(5, 3).then(result => console.log(result)); // 15

// 动态更新函数
worker.updateFunction((a, b) => a + b);
worker.execute(5, 3).then(result => console.log(result)); // 8

6. 性能优化技巧

javascript

javascript 复制代码
// 1. Worker复用池
class WorkerPool {
    constructor(size = navigator.hardwareConcurrency) {
        this.pool = Array.from({ length: size }, () => this.createWorker());
        this.queue = [];
    }
    
    createWorker() {
        const script = `
            let cachedFunctions = new Map();
            
            self.onmessage = function(e) {
                const { funcCode, args, funcId } = e.data;
                
                let func;
                if (cachedFunctions.has(funcId)) {
                    func = cachedFunctions.get(funcId);
                } else {
                    func = eval('(' + funcCode + ')');
                    cachedFunctions.set(funcId, func);
                }
                
                const result = func(...args);
                self.postMessage({ result, funcId });
            };
        `;
        
        return new Worker(URL.createObjectURL(new Blob([script])));
    }
    
    execute(func, args) {
        return new Promise((resolve) => {
            const funcId = this.hashCode(func.toString());
            const worker = this.getAvailableWorker();
            
            worker.busy = true;
            worker.onmessage = (e) => {
                worker.busy = false;
                resolve(e.data.result);
                this.processQueue();
            };
            
            worker.postMessage({
                funcCode: func.toString(),
                args,
                funcId
            });
        });
    }
    
    hashCode(str) {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            hash = ((hash << 5) - hash) + str.charCodeAt(i);
            hash |= 0;
        }
        return hash;
    }
}

7. 安全增强版

javascript

javascript 复制代码
// 添加沙箱保护
function createSecureWorker(func) {
    const script = `
        // 创建安全沙箱
        const sandbox = {
            Math, Array, Object, JSON, Number, String,
            Date, RegExp, Promise, console
        };
        
        // 限制访问
        const proxy = new Proxy(sandbox, {
            has: () => true,
            get(target, prop) {
                if (prop in target) {
                    return target[prop];
                }
                throw new Error(\`禁止访问: \${prop}\`);
            }
        });
        
        const userFunction = (${func.toString()});
        
        self.onmessage = function(e) {
            try {
                // 在沙箱中执行
                const result = userFunction.bind(proxy)(...e.data);
                self.postMessage({ success: true, result });
            } catch (error) {
                self.postMessage({ 
                    success: false, 
                    error: error.message 
                });
            }
        };
    `;
    
    return new Worker(URL.createObjectURL(new Blob([script])));
}

你的代码的优点:

  1. 极简实现:一行代码完成函数序列化和Worker创建

  2. 自执行模式:函数立即执行,适合一次性任务

  3. 零通信开销:不需要额外的postMessage调用

  4. 闭包友好:可以携带当前作用域的变量

注意事项:

  • 立即执行:Worker创建后立即执行,无法传递动态参数

  • 一次性:函数执行一次后Worker就结束了

  • 内存泄漏 :需要手动 URL.revokeObjectURL(blobURL)

  • 错误处理:缺少错误捕获机制


你的这种模式非常适合计算密集型的一次性任务,代码简洁而高效!

相关推荐
捧 花15 天前
从同步生成到异步任务:YoudaoNoteLM 的生成队列模块设计
go·agent·worker·eino
╰つ栺尖篴夢ゞ4 个月前
HarmonyOS Next面试题之Worker的运行机制和消息通信
worker·postmessage·actor 并发模型·异步消息队列·宿主线程通信
亿牛云爬虫专家7 个月前
Worker越简单,系统越稳定:从单机到集群
爬虫·python·集群·爬虫代理·单机·代理ip·worker
Irene19918 个月前
Worker() 构造函数第二个参数详解(附:凭证模式详解)
worker
非凡的世界10 个月前
ThinkPHP6 集成TCP长连接 GatewayWorker
网络·网络协议·tcp/ip·gateway·thinkphp·worker·workman
dangkei1 年前
详细分析CORS 工作原理
前后端分离·跨域·cloudflare·worker
ZHOU西口2 年前
微服务实战系列之玩转Docker(十二)
docker·云原生·raft·swarm·manager·docker swarm·worker
applebomb2 年前
uniapp小程序多线程 Worker 实战【2024】
小程序·uniapp·worker·unibest·异步线程
康康这名还挺多2 年前
鸿蒙 harmonyos 线程 并发 总结 async promise Taskpool woker(三)多线程并发 Worker
华为·harmonyos·并发·worker