CGraph: A Header-Based DAG Scheduler for C++ and Python Task Graphs
【A common used C++ & Python DAG framework】 一个通用的、无三方依赖的、跨平台的、收录于awesome-cpp的、基于流图的并行计算框架。欢迎star & fork & 交流
At a glance
- What is it?
- CGraph is a dependency-free C++11 directed acyclic graph framework with a Python binding, where nodes subclass GNode and dependencies are declared at registration time. It suits engineers who need explicit, in-process task ordering without pulling in a build system or a third-party runtime.
- Who is it for?
- Adopt CGraph if you are writing C++11 or Python code that needs explicit task ordering, parallel fan-out, and aggregation inside a single process, and you would rather subclass GNode than add a build-time dependency. Do not adopt it if your workflow spans processes, machines, or long-lived durable state, because the framework schedules threads, not distributed jobs.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Scheduling Problem CGraph Actually Solves
Most task code starts as a chain of function calls and grows into a graph by accident. Someone adds a step that can run in parallel, then a step that must wait for two others, then a retry path, and the ordering logic ends up scattered across threads, conditionals, and join points. CGraph takes the position that the ordering should be declared once, in one place, and executed by a scheduler that understands the shape of the work.
The README states the intent directly: you can "build your own operators simply, and describe any running schedules" for dependence, parallelling, aggregation, and conditional execution. The unit of work is a GNode subclass whose run() method returns a CStatus. The unit of coordination is a GPipeline, which holds the nodes and their edges and executes them. That is the whole model, and it is narrow on purpose.
The audience is narrower than the topic list suggests. The repository tags include ai, ai-agents, workflow, and langgraph, but nothing in the supplied material describes model inference, agent loops, or serialization formats. What the README does describe is in-process concurrency: nodes on separate threads, joined at dependency boundaries. If you are building an agent runtime on top of that, CGraph is the executor underneath, not the agent framework.
How GPipeline Orders Nodes and When It Runs Them in Parallel
The mechanism is visible in the introductory demo. Four nodes are registered with explicit dependency sets: a has no dependencies, b depends on a, c depends on a, and d depends on both b and c. The README walks through the resulting schedule: a runs first, then b and c run in parallel, and d runs only after both finish. Nothing in the demo names a thread or a mutex, which is the point. The pipeline derives the parallelism from the edges.
Each node is a class that inherits GNode and overrides run(), returning a CStatus. The status object is how a node reports failure, and since the framework knows the dependency graph, a failed node can prevent its dependents from starting. The demo uses CGRAPH_SLEEP_SECOND as a macro for its placeholder work, which tells you the examples are written to be read rather than run in production.
Registration is typed. The C++ call `pipeline->registerGElement<MyNode1>(&a, {}, "nodeA")` takes the node class as a template parameter, an out-pointer for the element handle, the dependency set, and a name. That name appears in the printf output, so node identity is a first-class concept rather than a debugging afterthought.
Beyond the flat graph, the README mentions GGroup as a container for multiple nodes that lets you "control the graph's conditional judgement, loop, and concurrent execution logic." That is the extension point for anything the plain edge list cannot express. The README does not show a GGroup example, so the exact API for conditions and loops has to come from the linked articles on chunel.cn, which cover run logic, loop logic, parameter passing, condition judgement, aspect-oriented hooks, function injection, messaging, event triggering, and timeouts.
Building It: CMake, the Header Include, and pycgraph
The C++ side has no third-party dependencies, so the build is limited to your own toolchain. The README points to a separate COMPILE.md for build and install instructions rather than embedding them, and it states the project is written against the pure C++11 standard library and targets MacOS, Linux, Windows, and Android. If your compiler is C++11-capable, there is nothing else to install.
Usage starts with a single include. The demo opens with `#include "CGraph.h"` and `using namespace CGraph;`. The pipeline lifecycle is explicit: `GPipelineFactory::create()` returns a GPipelinePtr, you register elements against it, call `process()`, and then call `GPipelineFactory::remove(pipeline)` to release resources. Forgetting that last call is the kind of thing that shows up as a leak in a long-running service, so treat create and remove as a pair.
The Python side is a package install. The README gives `pip install pycgraph` as the first line of the Python example, and the module imports are `from pycgraph import GNode, GPipeline, CStatus`. The API mirrors the C++ version closely: you subclass GNode, implement run(), construct a GPipeline, and call `pipeline.registerGElement(a, set(), "nodeA")` with a node object, a set of dependency nodes, and a name. The Python example registers the same four-node diamond, so the two languages share a mental model.
One difference worth noting: the Python registration takes node instances, while the C++ registration takes a template parameter and writes a handle into a pointer. The Python form is easier to read; the C++ form lets the pipeline own the construction. Neither is documented as a performance decision in the supplied material, so I would not read one into it.
Where the Design Gets in the Way
The dependency-free promise has a cost. With no third-party libraries, CGraph also has no third-party ecosystem: no serialization format for graphs, no visualization tooling beyond the images in the repository, no scheduler that survives a process restart. A graph defined in code is a graph you cannot reload from a file without writing that layer yourself.
The concurrency model is thread-based and in-process. That means the parallelism ceiling is the machine you are on, and the failure model is the failure model of a thread pool. If a node blocks on a network call, it holds a worker. The README's timeout feature, described in the linked articles as "超时机制", is the mitigation, but timeouts on individual nodes are not the same as durable retries or task persistence.
GGroup is the part I would want to see before committing. The README says it controls conditional judgement, loops, and concurrent execution, which is a lot of semantics to put behind one class, and the supplied material contains no example of it. Conditional and loop logic is exactly where DAG frameworks get awkward, because a loop introduces a cycle into a structure whose name promises acyclicity. How CGraph reconciles that is not stated here. Read the loop and condition articles on chunel.cn before you design around GGroup.
Finally, the repository is a single-maintainer project with a steady release cadence (v3.2.3 through v3.2.5 across roughly five months in 2026, per the release list) and MIT licensing. That is a reasonable bus-factor profile for a library you vendor, and a less comfortable one for a library you depend on for critical scheduling.
CGraph Against Taskflow and Raw Thread Pools
The closest comparison in the same space is Taskflow, a C++ task graph library that also lets you declare dependencies between tasks and executes them in parallel. The difference in approach is ownership. Taskflow's model is built around composing tasks and executors, with a wider set of features layered on top. CGraph's model is built around subclassing GNode and registering instances into a GPipeline that you create and destroy explicitly. If you want your work units to be classes with named identity and a status return, CGraph's shape fits more naturally. If you want to compose tasks as values and hand them to an executor you control, Taskflow's shape fits better. The repository itself acknowledges the overlap by tagging taskflow alongside langgraph and pipeline.
The other alternative is no framework at all: std::thread, std::async, and a few condition variables. That works for a fixed pipeline with two or three stages. It stops working when the graph changes shape, when you need to know which node failed, or when you want the same code to run under different dependency configurations. CGraph's value is that the graph is data you can reason about, not control flow you have to trace.
There are also sibling projects listed in the README with the same API shape in other languages: CsCGraph for C#, JaCGraph for Java, GoCGraph for Go, and CGraph-lite, described as a one-header-only C++ version. If you need the same graph model in a polyglot codebase, that family is the reason to standardize on this API rather than a competitor's.
Maintenance, Releases, and the MIT Licence
The release history shows v3.2.3 in March 2026, v3.2.4 in May 2026, and v3.2.5 in August 2026, with the last push to the default branch in September 2026. That is a cadence of roughly one minor release per quarter, which suggests active maintenance without promising a stability guarantee. There is no stated long-term support policy in the supplied material, and no versioning document explaining what a minor bump can break.
Upgrade cost is hard to estimate from the outside. The public API shown in the README is small (registerGElement, process, the GNode base class, the factory), and small APIs tend to change slowly. But GGroup's semantics are not documented in the README, so any upgrade that touches group behavior is an upgrade you would need to test rather than reason about. Pin a version in your build and read the release notes before moving.
The licence is MIT, which permits commercial and closed-source use with the usual requirement to preserve the copyright notice and permission notice. That is a permissive choice with no copyleft obligation, and it is a meaningful difference from frameworks released under GPL-family licences. This is a description of the licence text, not legal advice; have your own counsel review it if the distinction matters to your organization. There is no separate commercial edition or dual-licensing scheme mentioned in the README.
Editorial conclusion
Adopt CGraph if you are writing C++11 or Python code that needs explicit task ordering, parallel fan-out, and aggregation inside a single process, and you would rather subclass GNode than add a build-time dependency. Do not adopt it if your workflow spans processes, machines, or long-lived durable state, because the framework schedules threads, not distributed jobs. Before committing, verify three things against your own build: that your toolchain compiles the C++11 sources cleanly on your target platform, that the pycgraph wheel on PyPI matches your Python version, and that GGroup's condition and loop semantics cover the branching you actually need, since the README describes those features without specifying their exact API.
Community notes