# Thread-Safety Considerations When Mixing Libfiber Fibers with pthreads

> Learn thread-safety considerations for mixing libfiber with pthreads. Discover how to avoid race conditions with independent schedulers and exclusive synchronization primitive use.

- Repository: [iQIYI/libfiber](https://github.com/iqiyi/libfiber)
- Tags: best-practices
- Published: 2026-03-04

---

**Libfiber fibers are cooperative and bound to a single OS thread, requiring independent schedulers per pthread and exclusive use of library synchronization primitives like `fiber_mutex` and `go_wait_thread` to avoid race conditions.**

When building high-concurrency applications with **iqiyi/libfiber**, developers often need to combine lightweight cooperative fibers with traditional POSIX threads (pthreads). Understanding the **thread-safety considerations when mixing fibers with pthreads** is critical to prevent deadlocks, data races, and undefined behavior, as libfiber’s user-level scheduling model imposes strict binding between fibers and their host OS threads.

## Understanding the Fiber-to-Thread Binding Model

Libfiber implements **user-level, cooperative fibers** scheduled by the library itself. Each fiber runs inside exactly one OS thread, and the relationship is thread-local. In [`cpp/src/fiber.cpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/fiber.cpp) (lines 53-56), the `self()` method retrieves the current fiber ID only for the calling thread:

```cpp
unsigned long long fiber::self(void) {
    return acl_fiber_self();  // Returns ID of fiber running on this thread only
}

```

Attempting to call `fiber::self()` from a thread that never entered the libfiber scheduler results in undefined behavior. Always initialize the scheduler first.

## Initializing Schedulers on Every OS Thread

The event loop in libfiber is **per-thread**. In [`cpp/src/fiber.cpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/fiber.cpp) (lines 82-89), `schedule()` initializes a thread-local scheduler queue:

```cpp
void fiber::schedule(void) {
    acl_fiber_schedule();  // Starts the event loop for this thread only
}

```

To use fibers across multiple pthreads, each thread must call `acl::fiber::schedule()` independently. Fibers created within one pthread never migrate to another pthread’s ready-queue, preserving isolation.

## Safe Communication Between Fibers and pthreads

### Using Thread-Safe Synchronization Primitives

When sharing data between fibers and pthreads, use libfiber’s **thread-safe primitives** instead of standard mutexes. These wrap `pthread_mutex` and `pthread_cond` internally. In [`c/src/sync/fiber_mutex.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/sync/fiber_mutex.c), the core implementation ensures cross-thread safety:

```cpp
// C++ wrapper in cpp/src/fiber_mutex.cpp (lines 7-38)
class fiber_mutex {
    // Wraps acl_fiber_mutex_* which uses pthread_mutex internally
};

```

Safe primitives include:

- `fiber_mutex` – mutual exclusion between fibers and threads
- `fiber_cond` – condition variables
- `fiber_sem` – semaphores
- `fiber_event` – event signaling
- `fiber_tbox` – typed message boxes
- `wait_group` – synchronization barriers

### Spawning pthreads from Fibers with go_wait_thread

The `go_wait_thread` macro (defined in [`cpp/include/fiber/go_fiber.hpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/include/fiber/go_fiber.hpp), lines 64-73) safely creates a `std::thread` from within a fiber and blocks the fiber until the thread completes:

```cpp
go_wait_thread[&] {
    // This lambda runs in a new std::thread
    usleep(50000);
    shared_data = 42;
    box.push(nullptr);  // Signal completion
};

```

The fiber blocks safely via `fiber_tbox`, which uses `fiber_mutex` internally, preventing race conditions between the fiber and the spawned pthread.

## Shared Resources and External Locking

The `fiber_pool` class manages fiber lifecycles but does **not** provide internal locking for cross-thread access. In [`cpp/src/fiber_pool.cpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/fiber_pool.cpp) (lines 46-51 and 84-86), the `fibers_` set is modified without synchronization:

```cpp
// fiber_pool.cpp - unsynchronised modifications
void fiber_pool::fiber_create(...) {
    fibers_.insert(...);  // No internal lock
}

```

When sharing a `fiber_pool` across multiple pthreads, protect public API calls (`fiber_create`, `stop`, `schedule`) with an external `std::mutex`, or maintain separate pools per thread.

## Summary

- **Bind fibers to threads**: Each pthread must call `acl::fiber::schedule()` to initialize its own scheduler; fibers never migrate between OS threads.
- **Use library primitives**: Synchronize between fibers and pthreads exclusively through `fiber_mutex`, `fiber_cond`, `fiber_tbox`, and related thread-safe types.
- **Spawn safely**: Use `go_wait_thread` to launch pthreads from fibers and wait for completion without manual locking.
- **Protect shared pools**: When using `fiber_pool` across threads, apply external locking or use thread-local pool instances.
- **Avoid raw access**: Never manipulate fiber object internals (e.g., `fb->i_`) from a different thread without synchronization.

## Frequently Asked Questions

### Can a fiber migrate between different pthreads during execution?

No. Libfiber fibers are **strictly bound** to the OS thread that created them. The scheduler in [`cpp/src/fiber.cpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/fiber.cpp) maintains thread-local state, and a fiber’s context remains on the same pthread until it exits. Migration would require explicit stack copying and scheduler coordination, which the library does not support.

### Is it safe to call `fiber::self()` from a plain pthread that never initialized libfiber?

No. Calling `acl::fiber::self()` from a thread that never entered the scheduler returns undefined values or crashes. In [`cpp/src/fiber.cpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/fiber.cpp) (lines 53-56), the function assumes the calling thread has valid thread-local storage initialized by `acl::fiber::schedule()` or `acl::fiber::init()`.

### What happens if I use a standard `std::mutex` instead of `fiber_mutex` between a fiber and a pthread?

Using `std::mutex` can cause **deadlocks** or **priority inversion**. If a fiber locks a `std::mutex` and then yields (e.g., via I/O), the pthread waiting on that mutex may block indefinitely because the fiber scheduler cannot resume the fiber until the mutex owner releases it. Libfiber’s `fiber_mutex` yields control safely and integrates with the scheduler.

### Can I share a single `fiber_pool` instance across multiple pthreads without external locking?

No. The `fiber_pool` implementation in [`cpp/src/fiber_pool.cpp`](https://github.com/iqiyi/libfiber/blob/main/cpp/src/fiber_pool.cpp) (lines 46-51, 84-86) modifies internal containers like `fibers_` without synchronization primitives. Concurrent access from multiple pthreads causes data races. You must protect `fiber_pool` method calls with an external `std::mutex` or create separate pool instances per thread.