Metadata-Version: 2.1
Name: forkx
Version: 0.1.1
Summary: Ultra-simple parallel processing
Home-page: https://github.co.jp/
Author: bib_inf
Author-email: contact.bibinf@gmail.com
License: CC0 v1.0
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Classifier: License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
Description-Content-Type: text/markdown
Requires-Dist: ezpip

English description follows Japanese.

---

### 概要

`forkx` は、Python で「超」簡単に並行処理を書くための小さなユーティリティです。

複数の「引数なし関数（呼び出し可能オブジェクト）」を `forkx` に渡すと、それらが同一プロセス内で並行実行され、すべての処理が終わったあとで、結果がリストとして返されます。結果の順番は、渡した関数の順番と一致します。

ポイント:

- 複雑なワーカー管理なしで複数処理を同時に走らせたい
- オブジェクトをコピーせずに共有したい
- 書き込み競合のときだけロックで守りたい

といった用途に向きます。

> 注意: これはプロセス間の「並列処理」ではなく、同一プロセス内での「並行処理」です。

### 特徴

- **とにかく簡単な API**
  - `forkx(fn1, fn2, ...)` と書くだけ
  - 返り値は `[fn1の結果, fn2の結果, ...]`
- **共通オブジェクトをそのまま共有**
  - 同じメモリ空間上で、オブジェクトを複製せずにアクセスできます
- **ロック機能付き**
  - `forkx.Lock()` で、書き込み競合を避けるためのロックを簡単に利用できます
- **例外の扱い**
  - 各タスク内で発生した例外は呼び出し元に伝播します
  - ただし「すべてのタスクが終了したあと」でスローされます（途中で即中断しない）

---

### 基本的な使い方

```python
import time
import forkx

def proc_a():
	time.sleep(1)
	return 2**2

def proc_b():
	time.sleep(1)
	return 3**3

# proc_a, proc_b が並行に実行される
results = forkx(proc_a, proc_b)
print(results)  # -> [4, 27]
```

**動作:**

- `proc_a` と `proc_b` がほぼ同時に開始されます。
- `forkx` は両方の関数が終了するまで待ちます。
- 返ってくるリストの順番は、渡した順 (`proc_a`, `proc_b`) と同じです。

---

### 共通オブジェクトとロック

全タスクは同一プロセス・同一メモリ空間を共有するので、Python オブジェクトをそのまま共通で扱えます。
複数タスクから同じオブジェクトへ書き込みが発生する場合は、`forkx.Lock` で保護してください。

```python
import time
import forkx

common = {"cnt": 0}
lock = forkx.Lock()  # ロックオブジェクト

def cnt_up():
	time.sleep(1)
	# ロックで書き込み競合を防ぐ
	with lock:
		common["cnt"] += 1

# 同一関数も並行実行可能
forkx(*[cnt_up for _ in range(10)])

print(common)  # 例: {'cnt': 10}
```

**動作:**

- すべての `cnt_up` が同じ `common` を共有します。
- `with lock:` により、`common["cnt"]` の更新は1回ずつ順番に行われます。
- 全タスク完了後、`common["cnt"]` には矛盾のない最終値が入ります。

---

### 引数付きの処理を並行実行する

`forkx` に渡すのは「引数なし」の呼び出し可能オブジェクトです。
引数が必要な場合は、外側で引数をキャプチャするラッパ関数や lambda を使います。

```python
import forkx

def gen_func(i):
	return lambda:i**2

funcs = [gen_func(i) for i in range(5)]
print(forkx(*funcs))  # -> [0, 1, 4, 9, 16]
```

---

### 例外処理

タスク内で例外が発生した場合:

- その例外は `forkx` 呼び出し元に伝播します。
- ただし、他のタスクも含めて「すべての処理が終わってから」例外がスローされます。

つまり:

- どれか1つが失敗しても、他のタスクは最後まで実行されます。
- 呼び出し側から見ると、「全タスクが終わった段階」で成功か失敗かを判断できます。

（どの例外がどのような形で伝播するかは実装に依存します。）

---

### 備考

- `forkx` は、重厚な並列処理フレームワークではなく、「ちょっと並行実行したい」場面向けの軽量ツールです。
- 特に以下のようなケースに向きます:
  - `time.sleep` や I/O 待ちなど、待ち時間の長い処理をまとめて走らせたい
  - 共有オブジェクトをシンプルに扱いたい
  - 成功・失敗を「すべての処理が終わった時点」でまとめて確認したい

---
# forkx Documentation

---

## English

### Overview

`forkx` is a tiny utility for ultra-simple concurrent execution in Python.

You pass multiple *callable- objects to `forkx`, and it runs them concurrently in a single process while sharing the same memory space. After all tasks complete, `forkx` returns a list of results in the same order as the callables you passed in.

This makes it easy to:

- Run independent tasks concurrently
- Share mutable objects between tasks without manual worker setup
- Control write conflicts with a simple lock

> Note: This is *concurrent- execution within one process, not multi-process parallelism with separate memory.

---

### Key Features

- **Ultra-simple API**

  - Just call `forkx(fn1, fn2, ...)`
  - Returns `[fn1_result, fn2_result, ...]`
- **Shared memory**

  - All tasks can access and modify common objects directly (no copying)
- **Lock support**

  - `forkx.Lock()` provides a simple context manager to avoid write conflicts
- **Exception propagation**

  - Exceptions inside tasks are propagated to the caller
  - Propagation happens *after- all tasks have finished (no early abort)

---

### Basic Usage

```python
import time
import forkx

def proc_a():
	time.sleep(1)
	return 2**2

def proc_b():
	time.sleep(1)
	return 3**3

# proc_a and proc_b run concurrently
results = forkx(proc_a, proc_b)
print(results)  # -> [4, 27]
```

**Behavior:**

- Both `proc_a` and `proc_b` start roughly at the same time.
- `forkx` waits until *all- functions finish.
- The return value is a list of results in the order of the arguments (`proc_a`, `proc_b`).

---

### Shared State and Lock

Because all tasks run in the same process, you can safely share Python objects without serialization.
When multiple tasks write to shared data, you should use `forkx.Lock` to avoid race conditions.

```python
import time
import forkx

common = {"cnt": 0}
lock = forkx.Lock()

def cnt_up():
	time.sleep(1)
	with lock:  # prevent concurrent writes
		common["cnt"] += 1

# The same function can be run concurrently
forkx(*[cnt_up for _ in range(10)])

print(common)  # e.g. {'cnt': 10}
```

**Behavior:**

- All `cnt_up` executions share the same `common` dict.
- `with lock:` ensures that increments are applied one by one without conflicts.
- After all tasks complete, `common["cnt"]` has the consistent final value.

---

### Using Arguments via Closures

`forkx` expects zero-argument callables.
To pass arguments, create small wrapper functions or use lambdas that capture variables from the outer scope.

```python
import forkx

def gen_func(i):
	return lambda:i**2

funcs = [gen_func(i) for i in range(5)]
print(forkx(*funcs))  # -> [0, 1, 4, 9, 16]
```

---

### Exception Handling

If a task raises an exception:

- The exception will propagate out of `forkx`.
- `forkx` waits until all tasks (including non-failing ones) are done, then raises.

This means:

- No task is silently abandoned because another failed.
- You can rely on "all tasks finished or failed" before handling the exception.

(The exact form of the propagated exception is implementation-dependent.)

---

### Notes

- `forkx` is intended for simple, lightweight concurrency scenarios.
- It is especially useful when:

  - Functions are I/O bound or have waits (`sleep`, network, disk).
  - You want a minimal API for "run these functions concurrently and give me all results, or raise if something went wrong."

---


