Skip to content
easy-web-worker
Interactive example · Concurrency

Three searches, one worker or three

Search three segments of numbers for the longest Collatz chain, with one Worker and then with three, and compare the time.

Try it hereLive example
Application preview

Loading the example…

collatz.worker.ts
import { defineWorker } from 'easy-web-worker/defineWorker';
import { longestChain } from './collatz';

const worker = defineWorker(({ onMessage }) => ({
  /** Searches one segment, reports how far it is, and says which worker did it. */
  longestChain: onMessage(([from, to]: [from: number, to: number], message) => ({
    ...longestChain(from, to, (percentage) => message.reportProgress(percentage, { worker: self.name })),
    worker: self.name,
  })),
}));

export type CollatzWorker = typeof worker;
Ready. Run the example to begin.Reset returns this example to its initial state.
Take it with you

A pool is an option, not a rewrite. The code that calls the worker does not change.

01

Press Run with 1 worker. The three segments finish one after another, because one Worker does one thing at a time.

02

Press Run with 3 workers. The three segments finish together, and each row shows the worker that searched it.

03

Compare both times. The answers are identical; the difference is the work that ran in parallel.

Read the full guide

The code

The task is a real computation: find the number in a range whose Collatz chain is the longest. Every number in the range has to be tried, so the work grows with the range.

collatz.ts
export interface Chain {
  /** The number the chain starts from. */
  start: number;
  /** How many steps it takes that number to reach 1. */
  steps: number;
}

/**
 * Collatz: halve an even number, turn an odd one into 3n + 1, repeat until 1.
 * Finds the number in `[from, to)` that takes the most steps. Every number has to be tried.
 * `onProgress` receives the percentage searched, about once per percent.
 */
export function longestChain(from: number, to: number, onProgress?: (percentage: number) => void): Chain {
  const longest: Chain = { start: Math.max(from, 1), steps: 0 };
  const every = Math.max(1, Math.floor((to - from) / 100));

  let nextReport = from + every;

  for (let candidate = longest.start; candidate < to; candidate++) {
    if (candidate === nextReport) {
      onProgress?.(((candidate - from) / (to - from)) * 100);
      nextReport += every;
    }

    let value = candidate;
    let steps = 0;

    while (value !== 1) {
      value = value % 2 === 0 ? value / 2 : 3 * value + 1;
      steps += 1;
    }

    if (steps > longest.steps) {
      longest.start = candidate;
      longest.steps = steps;
    }
  }

  return longest;
}

/** Splits `[0, limit)` in equal parts, one per task. */
export const segments = (limit: number, parts: number): [from: number, to: number][] =>
  Array.from({ length: parts }, (_, part) => [Math.floor((limit * part) / parts), Math.floor((limit * (part + 1)) / parts)]);

The worker searches one segment and returns the answer with its own name. One worker can only search one segment at a time.

collatz.worker.ts
import { defineWorker } from 'easy-web-worker/defineWorker';
import { longestChain } from './collatz';

const worker = defineWorker(({ onMessage }) => ({
  /** Searches one segment, reports how far it is, and says which worker did it. */
  longestChain: onMessage(([from, to]: [from: number, to: number], message) => ({
    ...longestChain(from, to, (percentage) => message.reportProgress(percentage, { worker: self.name })),
    worker: self.name,
  })),
}));

export type CollatzWorker = typeof worker;

The pool is the maxWorkers option. warmUpWorkers creates every Worker up front, so the first batch does not pay for starting them.

client.ts
import { createWorker } from 'easy-web-worker/createWorker';
import workerUrl from './collatz.worker?worker&url';
import type { CollatzWorker } from './collatz.worker';

/** The same worker file; `maxWorkers` decides how many native Workers share the calls. */
export const createCollatzWorker = (maxWorkers: number) =>
  createWorker<CollatzWorker>(workerUrl, {
    maxWorkers,
    warmUpWorkers: true,
    workerOptions: { type: 'module', name: 'worker' },
  });

Related documentation

Keep exploring

Another piece of the model.

↑↓ Navigate ↵ Open Esc Closeeasy-web-worker docs
Get started

Real Web Workers. Typed methods.
Cancelable promises.