NAME

Sidef::Types::Block::Fork - Fork object for parallel process execution

DESCRIPTION

This class implements a Fork object that represents a forked system process in Sidef. Fork objects are created by the ffork() method on Block objects and enable parallel computation by executing code in separate system processes. The Fork class allows you to manage forked processes, wait for their completion, and retrieve their computed values.

When a block is forked using ffork(), it creates a new system process that executes in parallel with the main program. The Fork object acts as a handle to this background process, providing methods to control and synchronize with it.

SYNOPSIS

# Basic forking - execute code in parallel
var fork = { say "Hello from fork!"; 42 }.ffork
var result = fork.get    # waits and returns 42

# Parallel quicksort example
func quicksort(arr {.len <= 1}) { arr }
func quicksort(arr) {
    var p = arr.pop_rand
    var forks = [
        quicksort.ffork(arr.grep { _ <= p }),
        quicksort.ffork(arr.grep { _ >  p }),
    ]
    forks[0].wait + [p] + forks[1].wait
}

say quicksort(@("a".."z") -> shuffle)

# Parallel calculations on multiple values
var nums = [
    1275792312878611,
    12345678915808973,
    1578070919762253,
    14700694496703910,
]

var factors = nums.map {|n| 
    prime_factors.ffork(n)
}.map { .wait }

say ((nums ~Z factors) -> max_by {|m| m[1][0] })

# Multiple independent forks
var fork1 = { slow_computation_1() }.ffork
var fork2 = { slow_computation_2() }.ffork
var fork3 = { slow_computation_3() }.ffork

# Do some work while forks run in background
do_other_work()

# Collect all results
var results = [fork1.get, fork2.get, fork3.get]

METHODS

new

Fork.new

Creates a new Fork object. This method is typically not called directly by user code. Instead, Fork objects are created automatically when calling ffork() on a Block object.

Returns a new Fork object.

get

fork.get

Waits for the forked process to complete and returns the value computed by the block. This method blocks execution until the forked process finishes. If the block returns a value, that value is returned by get().

Example:

var fork = { 2 + 2 }.ffork
var result = fork.get    # blocks until done, returns 4

Returns: The value computed by the forked block, or nil if no value was returned.

Aliases: join, wait

join

fork.join

Alias for get(). Waits for the forked process to complete and returns its computed value.

wait

fork.wait

Alias for get(). Waits for the forked process to complete and returns its computed value. This naming follows the common Unix convention for waiting on child processes.

Example:

var forks = []
for i in (1..10) {
    forks << { expensive_computation(i) }.ffork
}
var results = forks.map { .wait }

kill

fork.kill(signal)

Sends a signal to the forked process, typically to terminate it. The signal parameter specifies which signal to send to the process (e.g., SIGTERM, SIGKILL, SIGINT).

Parameters:

  • signal - The signal number or name to send to the process

Example:

var fork = { loop { sleep(1) } }.ffork
sleep(5)
fork.kill(9)    # Send SIGKILL (9) to terminate immediately

Returns: The result of the kill operation (typically a boolean or status code).

Note: Use caution with kill() as it may leave resources in an inconsistent state if the forked process is interrupted during critical operations.

CREATING FORKS

Fork objects are not typically instantiated directly. Instead, they are created by calling the ffork() method on a Block object:

var block = { expensive_computation() }
var fork = block.ffork    # Creates and starts a Fork object

# Or more commonly:
var fork = { expensive_computation() }.ffork

The ffork() method immediately starts executing the block in a new system process and returns a Fork object that can be used to wait for completion and retrieve the result.

PARALLEL COMPUTATION PATTERNS

Map-Reduce Pattern

Fork objects work well with functional programming patterns like map:

var data = [1, 2, 3, 4, 5]
var forks = data.map {|n| { n**2 }.ffork }
var results = forks.map { .wait }
say results    # [1, 4, 9, 16, 25]

Divide and Conquer

Recursive algorithms benefit from parallel execution:

func parallel_sum(arr) {
    return 0 if arr.is_empty
    return arr[0] if (arr.len == 1)
    
    var mid = arr.len // 2
    var left_fork = parallel_sum.ffork(arr.first(mid))
    var right_fork = parallel_sum.ffork(arr.last(arr.len - mid))
    
    left_fork.wait + right_fork.wait
}

Independent Tasks

Execute completely independent operations in parallel:

var download_fork = { download_file(url) }.ffork
var process_fork  = { process_data(data) }.ffork
var upload_fork   = { upload_results(results) }.ffork

# Wait for all to complete
download_fork.wait
process_fork.wait
upload_fork.wait

PERFORMANCE CONSIDERATIONS

  • Process Creation Overhead

    Creating system processes has overhead. Forking is most beneficial for computationally expensive tasks that take significantly longer than the fork creation time.

  • Number of Forks

    Creating too many concurrent forks can overwhelm the system. Consider limiting the number of parallel processes to match available CPU cores.

  • Memory Usage

    Each forked process has its own memory space. Large data structures will be duplicated, potentially consuming significant memory.

  • Communication Cost

    Values returned from forks must be serialized and passed between processes, which adds overhead for large data structures.

ALTERNATIVES

For lighter-weight concurrency, Sidef also provides:

  • Block.thr() - Creates a Perl thread (deprecated) or system fork if the forks Perl module is installed

SEE ALSO

  • Sidef::Types::Block::Block - The Block class that provides the ffork() method

  • Sidef Tutorial - Parallel Computation section

  • UNIX fork() system call documentation

EXAMPLES

Example 1: Parallel File Processing

var files = Dir.cwd.open.glob('*.txt')
var forks = files.map {|file|
    { process_file(file) }.ffork
}
forks.each { .wait }

Example 2: Parallel Prime Factorization

func prime_factors(n) {
    # Implementation here
}

var numbers = [12345, 67890, 98765, 43210]
var factors = numbers.map {|n|
    prime_factors.ffork(n)
}.map { .wait }

numbers.zip(factors).each {|pair|
    say "#{pair[0]} = #{pair[1].join(' × ')}"
}

Example 3: Timeout Pattern

var fork = { potentially_slow_operation() }.ffork
var timeout = 5  # seconds

# Note: Sidef doesn't have built-in timeout for forks
# You would need to implement timeout logic with sleep/kill
var start = Time.sec
loop {
    sleep(0.1)
    if (Time.sec - start > timeout) {
        fork.kill(15)  # SIGTERM
        die "Operation timed out"
    }
}

NOTES

  • Fork objects represent true system processes (using the Unix fork() system call), not lightweight threads

  • Each forked process has its own memory space and cannot directly modify variables in the parent process

  • Values are passed back to the parent process through inter-process communication when get()/wait()/join() is called

  • Always ensure you call wait(), get(), or join() on Fork objects to prevent zombie processes

  • Forking may not be available or may behave differently on non-Unix systems (e.g., Windows)