This is an automated email from the ASF dual-hosted git repository.

wwbmmm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new 32847464 docs(bthread): translate English bthread documentation (#3503)
32847464 is described below

commit 32847464f3e50f7092d5f25bec31101d8e73bfd8
Author: darion-yaphet <[email protected]>
AuthorDate: Sun Aug 30 21:43:23 2026 +0800

    docs(bthread): translate English bthread documentation (#3503)
    
    English bthread pages were placeholders, so README and overview
    linked readers to empty stubs. Fill in the translations and add
    cross-language links.
---
 docs/cn/bthread.md                   |   2 +
 docs/cn/bthread_id.md                |   2 +
 docs/cn/bthread_or_not.md            |   2 +
 docs/cn/bthread_tagged_task_group.md |   1 +
 docs/cn/bthread_tracer.md            |   2 +
 docs/cn/execution_queue.md           |   2 +
 docs/en/bthread.md                   |  68 ++++++++++-
 docs/en/bthread_id.md                |  41 ++++++-
 docs/en/bthread_or_not.md            |  67 ++++++++++-
 docs/en/bthread_tagged_task_group.md |  59 ++++++++-
 docs/en/bthread_tracer.md            | 224 ++++++++++++++++++++++++++++++++++-
 docs/en/execution_queue.md           | 220 +++++++++++++++++++++++++++++++++-
 docs/en/overview.md                  |   4 +-
 13 files changed, 668 insertions(+), 26 deletions(-)

diff --git a/docs/cn/bthread.md b/docs/cn/bthread.md
index f0de58bc..80905098 100644
--- a/docs/cn/bthread.md
+++ b/docs/cn/bthread.md
@@ -1,3 +1,5 @@
+[English version](../en/bthread.md)
+
 
[bthread](https://github.com/apache/brpc/tree/master/src/bthread)是brpc使用的M:N线程库,目的是在提高程序的并发度的同时,降低编码难度,并在核数日益增多的CPU上提供更好的scalability和cache
 
locality。”M:N“是指M个bthread会映射至N个pthread,一般M远大于N。由于linux当下的pthread实现([NPTL](http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library))是1:1的,M个bthread也相当于映射至N个[LWP](http://en.wikipedia.org/wiki/Light-weight_process)。bthread的前身是Distributed
 Process(DP)中的fiber,一个N:1的合作式线程库,等价于event-loop库,但写的是同步代码。
 
 # Goals
diff --git a/docs/cn/bthread_id.md b/docs/cn/bthread_id.md
index d6ba8a2c..34f43297 100644
--- a/docs/cn/bthread_id.md
+++ b/docs/cn/bthread_id.md
@@ -1,3 +1,5 @@
+[English version](../en/bthread_id.md)
+
 
bthread_id是一个特殊的同步结构,它可以互斥RPC过程中的不同环节,也可以O(1)时间内找到RPC上下文(即Controller)。注意,这里我们谈论的是bthread_id_t,不是bthread_t(bthread的tid),这个名字起的确实不太好,容易混淆。
 
 具体来说,bthread_id解决的问题有:
diff --git a/docs/cn/bthread_or_not.md b/docs/cn/bthread_or_not.md
index 2b71c90d..0ba33d5f 100644
--- a/docs/cn/bthread_or_not.md
+++ b/docs/cn/bthread_or_not.md
@@ -1,3 +1,5 @@
+[English version](../en/bthread_or_not.md)
+
 brpc提供了[异步接口](client.md#异步访问),所以一个常见的问题是:我应该用异步接口还是bthread?
 
 短回答:延时不高时你应该先用简单易懂的同步接口,不行的话用异步接口,只有在需要多核并行计算时才用bthread。
diff --git a/docs/cn/bthread_tagged_task_group.md 
b/docs/cn/bthread_tagged_task_group.md
index 027bd4eb..038eaead 100644
--- a/docs/cn/bthread_tagged_task_group.md
+++ b/docs/cn/bthread_tagged_task_group.md
@@ -1,3 +1,4 @@
+[English version](../en/bthread_tagged_task_group.md)
 
 # Bthread tagged task group
 
diff --git a/docs/cn/bthread_tracer.md b/docs/cn/bthread_tracer.md
index bab09ce4..f92b356d 100644
--- a/docs/cn/bthread_tracer.md
+++ b/docs/cn/bthread_tracer.md
@@ -1,3 +1,5 @@
+[English version](../en/bthread_tracer.md)
+
 gdb(ptrace)+ gdb_bthread_stack.py主要的缺点是要慢和阻塞进程,需要一种高效的追踪bthread调用栈的方法。
 
 bRPC框架的协作式用户态协程无法像Golang内建的抢占式协程一样实现高效的STW(Stop the 
World),框架也无法干预用户逻辑的执行,所以要追踪bthread调用栈是比较困难的。
diff --git a/docs/cn/execution_queue.md b/docs/cn/execution_queue.md
index 32d4d2d4..7814741d 100644
--- a/docs/cn/execution_queue.md
+++ b/docs/cn/execution_queue.md
@@ -1,3 +1,5 @@
+[English version](../en/execution_queue.md)
+
 # 概述
 
 类似于kylin的ExecMan, 
[ExecutionQueue](https://github.com/apache/brpc/blob/master/src/bthread/execution_queue.h)提供了异步串行执行的功能。ExecutionQueue的相关技术最早使用在RPC中实现[多线程向同一个fd写数据](io.md#发消息).
 在r31345之后加入到bthread。 ExecutionQueue 提供了如下基本功能:
diff --git a/docs/en/bthread.md b/docs/en/bthread.md
index 7c627f1b..9b8dd51c 100644
--- a/docs/en/bthread.md
+++ b/docs/en/bthread.md
@@ -1,7 +1,67 @@
-# uthread
+[中文版](../cn/bthread.md)
 
-This document has not yet been translated into English.
+# bthread
 
-Please refer to the [Chinese version](../cn/bthread.md) for the full content.
+[bthread](https://github.com/apache/brpc/tree/master/src/bthread) is the M:N 
threading library used by brpc. The goal is to raise concurrency, keep coding 
simple, and get better scalability and cache locality on CPUs with more and 
more cores. "M:N" means M bthreads are mapped onto N pthreads, and M is usually 
much larger than N. Because Linux pthread 
([NPTL](http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library)) is 1:1, 
those M bthreads are also mapped onto N [LWPs](http://en.wikiped [...]
 
-Contributions to translate this document are welcome. See 
[TRANSLATING](TRANSLATING) for guidelines.
+# Goals
+
+- Users keep the synchronous programming style, can create a bthread in a few 
hundred nanoseconds, and can synchronize with a variety of primitives.
+- Every bthread API is callable from a pthread and has reasonable behavior. 
Code that uses bthread APIs can run correctly inside a pthread.
+- Make full use of multiple cores.
+- Better cache locality; NUMA support is a plus.
+
+# Non-goals
+
+- Provide a pthread-compatible ABI that works just by linking. **Rejected 
because**: bthread has no priorities and is not suitable for every workload. 
Silent replacement by linking would make users pick up bthread without knowing 
it, and cause bugs.
+- Intercept every possibly blocking glibc function and syscall so that they 
block the bthread instead of the system thread. **Rejected because**:
+  - Blocking a bthread may switch the underlying system thread, so functions 
that depend on system TLS have undefined behavior.
+  - Mixing them with functions that block pthreads can deadlock.
+  - These hooks are usually slower, because they often need extra syscalls 
such as epoll. The same coverage is more useful for an N:1 cooperative library 
(fiber): the hook itself is slower, but without it the whole system thread 
blocks and every fiber stalls.
+- Patch the kernel so that pthread can switch quickly on the same core. 
**Rejected because**: with a large number of pthreads, per-thread resources are 
diluted and thread-local caches (for example tcmalloc) work poorly. A separate 
bthread library does not have this problem, because it still maps onto a small 
number of pthreads. A large part of bthread's speedup over pthread comes from 
concentrating thread resources. Portability also matters: bthread prefers pure 
userland code.
+
+# FAQ
+
+##### Q: Is bthread a coroutine?
+
+No. "Coroutine" here means an N:1 threading library: all coroutines run in one 
system thread. Compute power is equivalent to an event-loop library. Because 
they never leave that thread, switches need no syscall (about 100ns–200ns) and 
cache-coherence cost is small. The cost is that coroutines cannot use multiple 
cores well, and the code must be non-blocking, otherwise every coroutine 
stalls. That makes them a good fit for IO servers whose run time is 
deterministic, such as an HTTP server [...]
+
+bthread is an M:N threading library. One stalled bthread does not stall the 
others. The two key techniques are work-stealing scheduling and butex. The 
former schedules bthreads onto more cores quickly; the latter lets bthreads and 
pthreads wait for and wake each other. Neither is needed by a coroutine. See 
[threading overview](threading_overview.md) for more on threading models.
+
+##### Q: Should I create lots of bthreads in my program?
+
+No. Unless you need to [run some code concurrently inside one 
RPC](bthread_or_not.md), do not call bthread APIs directly. Leave that to brpc.
+
+##### Q: How do bthreads map onto pthread workers?
+
+A pthread worker runs exactly one bthread at a time. When the current bthread 
suspends, the worker first tries to pop a ready bthread from its local 
runqueue. If that is empty, it steals a ready bthread from a random other 
worker. If that also fails, it sleeps and is woken when a new ready bthread 
appears.
+
+##### Q: Can a bthread call blocking pthread or system functions?
+
+Yes. That only blocks the current pthread worker. Other pthread workers are 
unaffected.
+
+##### Q: Does one blocked bthread affect other bthreads?
+
+No. If the bthread blocks on a bthread API, it yields the current pthread 
worker to other bthreads. If it blocks on a pthread API or a system function, 
ready bthreads on that worker are stolen by idle pthread workers.
+
+##### Q: Can pthread code call bthread APIs?
+
+Yes. A bthread API called from a bthread affects the current bthread; called 
from a pthread, it affects the current pthread. Code that uses bthread APIs can 
run directly in a pthread.
+
+##### Q: If many bthreads call blocking pthread or system functions, does that 
hurt RPC?
+
+Yes. For example, with 8 pthread workers, if 8 bthreads all call `usleep()`, 
RPC code that handles network IO cannot run for a while. As long as the block 
is not too long, this usually **does not matter much**: the workers are all 
busy, and queuing is about the only option left.
+In brpc you can raise the worker count to mitigate this: on the server set 
[ServerOptions.num_threads](server.md#number-of-worker-pthreads) or 
[-bthread_concurrency](flags.md); on the client set 
[-bthread_concurrency](flags.md).
+
+Is there a way to avoid this completely?
+
+- Dynamically adding workers is the obvious idea, but it often fails in 
practice. When many workers block at once, they are often waiting for the same 
resource (for example the same lock). Adding workers may only add more waiters.
+- Split IO threads and worker threads? IO threads would only send and receive, 
so a fully blocked worker pool would not stall IO. An extra hop does not 
relieve congestion. If every worker is stuck, the program is still stuck; the 
stall just moves from the socket buffer to the queue between IO threads and 
workers. In other words, IO threads that keep running while workers are stuck 
may be doing useless work. That is what **does not matter much** above really 
means. Another cost is that ev [...]
+- A practical fix is to [limit max concurrency](server.md#limit-concurrency). 
If the number of in-flight requests stays below the worker count, "all workers 
blocked" does not happen.
+- Another fix: when blocked workers pass a threshold (for example 6 of 8), 
stop running user code in place and throw it into a separate thread pool. Even 
if all user code blocks, a few workers remain to handle RPC IO. bthread mode 
does not have this mechanism today, but a similar one is implemented when 
[pthread mode](server.md#pthread-mode) is on. Is that "useless work" while user 
code is fully blocked, as above? Possibly. The mechanism is more about avoiding 
a rare deadlock: all user c [...]
+
+##### Q: Will bthread have [Channel](https://gobyexample.com/channels)?
+
+No. A channel models a relationship between two points, while many real 
problems are many-to-many. The natural channel solution is then: one role owns 
a thing or a resource, and every other thread sends commands to that role over 
a channel. Give the program N roles, each doing its job, and the program runs 
in an orderly way. So using channels implies splitting the program into roles. 
Channels are intuitive, but they cost extra context switches. Nothing finishes 
until the callee is schedu [...]
+
+What we usually need is a buffered channel that acts as a queue with ordered 
execution. bthread provides [ExecutionQueue](execution_queue.md) for that.
diff --git a/docs/en/bthread_id.md b/docs/en/bthread_id.md
index ff67747f..52dd12d8 100644
--- a/docs/en/bthread_id.md
+++ b/docs/en/bthread_id.md
@@ -1,7 +1,40 @@
-# uthread id
+[中文版](../cn/bthread_id.md)
 
-This document has not yet been translated into English.
+# bthread_id
 
-Please refer to the [Chinese version](../cn/bthread_id.md) for the full 
content.
+`bthread_id` is a special synchronization structure. It can serialize 
different steps of an RPC, and it can find the RPC context (the Controller) in 
O(1) time. We are talking about `bthread_id_t` here, not `bthread_t` (the 
bthread tid). The name is unfortunate and easy to confuse.
 
-Contributions to translate this document are welcome. See 
[TRANSLATING](TRANSLATING) for guidelines.
+Concretely, `bthread_id` solves:
+
+- The response arriving while the request is still being sent, so response 
handling races with send code.
+- A timer firing immediately after it is set, so timeout handling races with 
send code.
+- Several responses from retries arriving at the same time and racing with 
each other.
+- Finding the RPC context from a `correlation_id` in O(1) time, without a 
global hash map from `correlation_id` to context.
+- Cancelling an RPC.
+
+Those bugs show up widely in other RPC frameworks. Here is how brpc uses 
`bthread_id` to close them.
+
+A `bthread_id` has two parts: a user-visible 64-bit id, and a hidden 
`bthread::Id` struct. User APIs all operate on the id. Mapping from id to 
struct is the same as [other structures](memory_management.md) in brpc: 32 bits 
are the pool offset, 32 bits are the version. The former locates in O(1); the 
latter avoids ABA.
+
+The `bthread_id` API is not small:
+
+- create
+- lock
+- unlock
+- unlock_and_destroy
+- join
+- error
+
+The extra APIs exist to cover different flows.
+
+- Send request: `bthread_id_create` → `bthread_id_lock` → … register timer and 
send RPC … → `bthread_id_unlock`
+- Receive response: `bthread_id_lock` → … process response → 
`bthread_id_unlock_and_destroy`
+- Error handling: timeout / socket fail → `bthread_id_error` → run the 
`on_error` callback (which takes the lock). Then either:
+  - Retry / backup request: register timer and send RPC again → 
`bthread_id_unlock`
+  - Cannot retry, final failure: `bthread_id_unlock_and_destroy`
+- Wait synchronously for the RPC to finish: `bthread_id_join`
+
+To cut waiting, `bthread_id` has a few extra mechanisms:
+
+- When an error arrives and the id is already locked, the error is pushed onto 
a pending queue and `bthread_id_error` returns immediately. On 
`bthread_id_unlock`, pending work is taken off the queue and run.
+- When the RPC finishes and there is a user callback, first call 
`bthread_id_about_to_destroy` so in-flight `bthread_id_lock` waiters fail 
immediately, then run the user callback (which may take a long, unpredictable 
time), and finally `bthread_id_unlock_and_destroy`.
diff --git a/docs/en/bthread_or_not.md b/docs/en/bthread_or_not.md
index 2450c3c0..52b0178c 100644
--- a/docs/en/bthread_or_not.md
+++ b/docs/en/bthread_or_not.md
@@ -1,7 +1,66 @@
-# uthread or not
+[中文版](../cn/bthread_or_not.md)
 
-This document has not yet been translated into English.
+# bthread or not
 
-Please refer to the [Chinese version](../cn/bthread_or_not.md) for the full 
content.
+brpc provides an [asynchronous API](client.md#asynchronous-call), so a common 
question is: should I use the async API or bthread?
 
-Contributions to translate this document are welcome. See 
[TRANSLATING](TRANSLATING) for guidelines.
+Short answer: when latency is not high, start with the simple synchronous API. 
If that is not enough, use the async API. Use bthread only when you need 
parallel compute across cores.
+
+# Sync or async
+
+Async means replacing blocking with callbacks: wherever you would block, you 
get a callback. Callbacks work well in JavaScript and are widely accepted 
there, but that is a different kind of callback from what server code needs. 
The difference is not 
[lambda](https://en.wikipedia.org/wiki/Anonymous_function) or 
[future](https://en.wikipedia.org/wiki/Futures_and_promises); it is that 
JavaScript is single-threaded. Drop those callbacks into a multi-threaded 
program and few of them would eve [...]
+
+Async in brpc is not single-threaded async. The callback runs on a different 
thread from the caller, so you get multi-core scalability, but you must deal 
with multi-threading. You can block inside the callback; as long as there are 
enough threads, overall server performance is fine. Async code is still hard to 
write, which is why we provide [combo channels](combo_channel.md): by composing 
channels you declare complex access patterns without sweating every detail.
+
+When latency is short and QPS is not high, we still recommend the sync API. 
That is also why bthread exists: keep synchronous code and still improve 
interactive performance.
+
+**Choosing sync or async**: compute `qps * latency` (latency in seconds). If 
the result is on the same order as the number of CPU cores, use sync; otherwise 
use async.
+
+Examples:
+
+- qps = 2000, latency = 10ms, result = 2000 * 0.01s = 20. Same order as a 
typical 32-core machine → sync.
+- qps = 100, latency = 5s, result = 100 * 5s = 500. Not the same order as core 
count → async.
+- qps = 500, latency = 100ms, result = 500 * 0.1s = 50. Roughly the same order 
→ sync is OK. If latency keeps growing, consider async.
+
+The formula is the average number of in-flight requests (try proving it). It 
is comparable to thread count and CPU cores. When it is much larger than the 
core count, most operations are not burning CPU; they are parking a lot of 
threads. Async then saves thread resources (stack memory) in a visible way. 
When the value is at or below the core count, the thread-resource savings from 
async are small, and simple sync code matters more.
+
+# Async or bthread
+
+With bthread you can even implement async yourself. Take "semi-sync" as an 
example. In brpc you have several options:
+
+- Start several async RPCs and Join them one by one. Join blocks until the RPC 
finishes. (This is only for comparison with bthread. In real code we recommend 
[ParallelChannel](combo_channel.md#parallelchannel) instead of joining by hand.)
+- Start several bthreads, each doing a sync RPC, then join the bthreads.
+
+Which is faster? The first. The second pays for creating bthreads, and those 
bthreads stay blocked for the whole RPC and cannot be used for anything else.
+
+**If you only need concurrent RPCs, do not use bthread.**
+
+Parallel compute is a different story. bthread makes it easy to build a tree 
of parallel work and use multiple cores. If a search has three stages that can 
run in parallel, start two bthreads for two stages, run the third in place, 
then join the two bthreads:
+
+```c++
+bool search() {
+  ...
+  bthread th1, th2;
+  if (bthread_start_background(&th1, nullptr, part1, part1_args) != 0) {
+    LOG(ERROR) << "Fail to create bthread for part1";
+    return false;
+  }
+  if (bthread_start_background(&th2, nullptr, part2, part2_args) != 0) {
+    LOG(ERROR) << "Fail to create bthread for part2";
+    return false;
+  }
+  part3(part3_args);
+  bthread_join(th1);
+  bthread_join(th2);
+  return true;
+}
+```
+
+Notes:
+
+- You could start three bthreads and join all of them, but that costs one 
extra thread resource compared with running one stage in place.
+- There is a delay from creating a bthread to running it (scheduling delay). 
On a machine that is not very busy, the median is about 3 microseconds, 90% 
finish within 10 microseconds, and 99.99% within 30 microseconds. Two 
consequences:
+  - The payoff is clear when the compute takes more than 1ms. If the work 
finishes in a few microseconds, bthread is not worth it.
+  - Run the slowest stage in place. Then even if the bthread stages are 
delayed by a few microseconds, they may still finish first, and the delay 
disappears. Joining an already-finished bthread returns immediately, with no 
context-switch cost.
+
+If you need something like a thread pool that runs one class of jobs, you can 
also replace the pool with bthreads. If job order matters, use bthread's 
[ExecutionQueue](execution_queue.md).
diff --git a/docs/en/bthread_tagged_task_group.md 
b/docs/en/bthread_tagged_task_group.md
index b509b31e..a80e64f1 100644
--- a/docs/en/bthread_tagged_task_group.md
+++ b/docs/en/bthread_tagged_task_group.md
@@ -1,7 +1,58 @@
-# uthread tagged task group
+[中文版](../cn/bthread_tagged_task_group.md)
 
-This document has not yet been translated into English.
+# Bthread tagged task group
 
-Please refer to the [Chinese version](../cn/bthread_tagged_task_group.md) for 
the full content.
+Many applications need to isolate thread resources. For example a service has 
a control plane and a data plane, and heavy data-plane traffic should not 
starve the control plane. Or a service has several disks, and threads serving 
different disks should not interfere with each other. Tagging bthread task 
groups splits the bthread worker pool by tag so that groups do not affect one 
another.
 
-Contributions to translate this document are welcome. See 
[TRANSLATING](TRANSLATING) for guidelines.
+Tagging is per server. Put services of different groups on different servers; 
those servers listen on different ports. Some background or timer jobs have no 
service at all and still need their own pool. You can give those jobs a 
dedicated tag, and you control that pool's concurrency yourself. On top of that 
you can add policies such as pinning a tag to a NUMA node or setting 
thread-local variables.
+
+The implementation creates several worker groups at the bthread layer; each 
group runs the same logic as before. The bthread API adds a `tag` field on 
`bthread_attr_t`. The RPC layer adds `bthread_tag` on `brpc::ServerOptions` so 
a server can pick which worker group it runs on.
+
+# Usage
+
+`example/bthread_tag_echo_c++` has a sample. Start the server and clients 
separately. The server splits workers into 3 tags. `FLAGS_tag1` and 
`FLAGS_tag2` tag different servers. The remaining tag is for background jobs.
+
+```bash
+# Server
+./echo_server -task_group_ntags 3 -tag1 0 -tag2 1 -bthread_concurrency 20 
-bthread_min_concurrency 8 -event_dispatcher_num 1
+
+# Clients
+./echo_client -dummy_port 8888 -server "0.0.0.0:8002" -use_bthread true
+./echo_client -dummy_port 8889 -server "0.0.0.0:8003" -use_bthread true
+```
+
+`FLAGS_bthread_concurrency` is the total number of threads. 
`FLAGS_bthread_min_concurrency` is the lower bound across all groups. 
`FLAGS_event_dispatcher_num` is the number of event dispatchers in one group. 
`FLAGS_bthread_current_tag` is the tag whose size you are about to change, and 
`FLAGS_bthread_concurrency_by_tag` sets that group's thread count.
+
+Ordinary bthreads do not need to set `bthread_attr_t.tag`; they run in the 
current tag context. To run a bthread on another tag, set `bthread_attr_t.tag` 
to that value. That costs some performance and should be avoided on the hot 
path.
+
+Q: How do I change a group's thread count at runtime?
+
+A: You can size each group more freely for your service. At startup the pool 
is initialized from `bthread_concurrency`. If you set 
`bthread_min_concurrency`, that value is used instead. For a server, 
`num_threads` is the worker count of that tag. Change a group's size with 
`FLAGS_bthread_current_tag` and `FLAGS_bthread_concurrency_by_tag`. If those 
are unset (tagging is off, default `BTHREAD_TAG_INVALID`), `num_threads` means 
the total worker count across all groups.
+
+Q: How do groups relate to each other?
+
+A: They are independent thread pools and event dispatchers. They do not 
interact.
+
+Q: Can I synchronize bthreads across groups?
+
+A: Yes. Each bthread keeps its own tag. After it suspends and runs again, it 
continues on that tag's pool.
+
+Q: On which group does a client send and receive RPC messages?
+
+A: It depends on the client context. If the client is not on any tag, tag 0 is 
used; otherwise the current tag is used.
+
+Q: How do I bind a group's threads to specific CPUs?
+
+A: `int bthread_set_tagged_worker_startfn(void (*start_fn)(bthread_tag_t))` 
runs initialization on a group. You can implement CPU binding there, using the 
`tag` argument to pin different groups to different CPUs.
+
+# Monitoring
+
+Metrics split by tag today: thread count, thread usage, `bthread_count`, and 
connection info.
+
+Thread usage: ![img](../images/bthread_tagged_worker_usage.png)
+
+Dynamically changing tag 1's thread count:
+
+Set tag 1: ![img](../images/bthread_tagged_increment_tag1.png)
+
+Set all tags: ![img](../images/bthread_tagged_increment_all.png)
diff --git a/docs/en/bthread_tracer.md b/docs/en/bthread_tracer.md
index 6df0d8d7..35a4cd05 100644
--- a/docs/en/bthread_tracer.md
+++ b/docs/en/bthread_tracer.md
@@ -1,7 +1,223 @@
-# uthread tracer
+[中文版](../cn/bthread_tracer.md)
 
-This document has not yet been translated into English.
+gdb (`ptrace`) plus `gdb_bthread_stack.py` is slow and blocks the process. We 
need a cheaper way to trace a bthread call stack.
 
-Please refer to the [Chinese version](../cn/bthread_tracer.md) for the full 
content.
+brpc's cooperative userland threads cannot do an efficient STW (Stop The 
World) the way Go's preemptive goroutines can, and the framework cannot 
interrupt user logic. Tracing a bthread stack is therefore hard.
 
-Contributions to translate this document are welcome. See 
[TRANSLATING](TRANSLATING) for guidelines.
+Online tracing has to solve:
+
+1. Tracing a suspended bthread's call stack.
+2. Tracing a running bthread's call stack.
+
+# bthread status model
+
+The current bthread status model:
+
+![bthread status model](../images/bthread_status_model.svg)
+
+# Design
+
+## Core idea
+
+To solve those two problems, this design implements STB (Stop The Bthread). 
While a bthread stack is being traced, the status must not move into a state 
that the current tracing method does not support. STB has two modes: context 
tracing and signal tracing.
+
+### Context tracing
+
+Context tracing covers suspended bthreads. A suspended stack is stable. Using 
the context saved in `TaskMeta.stack` (on x86_64 the important registers are 
mainly RIP, RSP, RBP), a library that can unwind a given context walks the 
stack. A suspended bthread may wake at any time, run (including `jump_stack`), 
and then the stack keeps changing. An unstable context cannot be unwound, so 
scheduling is intercepted before `jump_stack` and the bthread only continues 
after tracing finishes. Conte [...]
+
+### Signal tracing
+
+Signal tracing covers running bthreads. A running bthread is unstable, so 
`TaskMeta.stack` cannot be used. Instead a signal interrupts the bthread and 
the signal handler unwinds the stack. Signals bring two issues:
+
+1. Async-signal-safety.
+2. Signal tracing does not support `jump_stack`. Unwinding needs register 
state, and `jump_stack` mutates registers, so interrupting `jump_stack` is 
unsafe. Scheduling is intercepted before `jump_stack` and the bthread only 
suspends after tracing finishes.
+
+So this mode only supports the running state.
+
+### Summary
+
+`jump_stack` is on every path that suspends or runs a bthread, and it is the 
STB intercept point. STB splits states into three groups:
+
+1. Context-tracing states: ready, suspended.
+2. Signal-tracing states: running.
+3. Unsupported states. Neither method can unwind during `jump_stack`. 
Scheduling is intercepted before `jump_stack` and continues only after tracing 
finishes.
+
+### Flow
+
+After STB, two intercept states are added on top of the original model: 
about-to-run and about-to-suspend.
+
+![bthread STB status model](../images/bthread_stb_model.svg)
+
+STB flow:
+
+1. When TaskTracer (the STB module) receives a trace request, it marks tracing 
in progress. When tracing finishes, it marks completion and signals bthreads 
that may be in about-to-run or about-to-suspend. TaskTracer then branches on 
status:
+- created, ready but no stack yet, destroyed: finish immediately.
+- suspended, ready: context tracing.
+- running: signal tracing.
+- about-to-run, about-to-suspend: spin until the bthread moves to the next 
state (suspended or running), then continue.
+
+2. While TaskTracer is tracing, the bthread also branches on status:
+- created, ready but no stack yet, ready: nothing extra.
+- suspended, running: notify TaskTracer to continue.
+- about-to-run, about-to-suspend, destroyed: wait on a condition variable 
until TaskTracer finishes. After that, TaskTracer wakes the bthread to continue 
`jump_stack`.
+
+# Usage
+
+1. Install libunwind and abseil-cpp. **Note: libunwind must be built from 
source. Do not use the distro package `libunwind-dev` / `libunwind-devel`**, or 
you will hit the crash in [Known issue: libunwind and libgcc_s `_Unwind_*` 
symbol conflict](#known-issue-libunwind-and-libgcc_s-_unwind_-symbol-conflict). 
Bazel builds can skip this step and use the libunwind version maintained in the 
brpc repo.
+2. Pass `--with-bthread-tracer` to `config_brpc.sh`, or 
`-DWITH_BTHREAD_TRACER=ON` to cmake, or `--define with_bthread_tracer=true` to 
bazel (Bzlmod).
+3. Hit the builtin service `http://ip:port/bthreads/<bthread_id>?st=1`, or 
call `bthread::stack_trace()` in code.
+4. To trace a pthread, call `bthread::init_for_pthread_stack_trace()` on that 
pthread to get a fake `bthread_t`, then use step 3.
+
+Example output:
+
+```shell
+#0 0x00007fdbbed500b5 __clock_gettime_2
+#1 0x000000000041f2b6 butil::cpuwide_time_ns()
+#2 0x000000000041f289 butil::cpuwide_time_us()
+#3 0x000000000041f1b9 butil::EveryManyUS::operator bool()
+#4 0x0000000000413289 (anonymous namespace)::spin_and_log()
+#5 0x00007fdbbfa58dc0 bthread::TaskGroup::task_runner()
+```
+
+# Known issues
+
+## Known issue: libunwind and libgcc_s `_Unwind_*` symbol conflict
+
+### Symptom
+
+With bthread tracer enabled, you may see an occasional segfault on 
`bthread_exit` / `pthread_exit` or on a C++ exception path, with a stack like:
+
+```text
+#0  0x0000000000000000 in ?? ()
+#1  0x00007fa2b5d6458a in _ULx86_64_dwarf_find_proc_info ()
+   from 
/root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so
+#2  0x00007fa2b5d6668d in fetch_proc_info ()
+   from 
/root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so
+#3  0x00007fa2b5d681a1 in _ULx86_64_dwarf_make_proc_info ()
+   from 
/root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so
+#4  0x00007fa2b5d70cfd in _ULx86_64_get_proc_info ()
+   from 
/root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so
+#5  0x00007fa2b5d6c775 in __libunwind_Unwind_GetLanguageSpecificData ()
+   from 
/root/.cache/bazel/_bazel_root/743b333b2429a1dbd390ef66b59c771d/execroot/_main/bazel-out/k8-fastbuild/bin/test/../_solib_k8/libexternal_Slibunwind~_Slibunwind.so
+#6  0x00007fa2b503c6df in __gxx_personality_v0 () from 
/lib/x86_64-linux-gnu/libstdc++.so.6
+#7  0x00007fa2b5452ce5 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1
+#8  0x00007fa2b54533c0 in _Unwind_ForcedUnwind () from 
/lib/x86_64-linux-gnu/libgcc_s.so.1
+#9  0x00007fa2b4ca57a4 in __GI___pthread_unwind (buf=<optimized out>) at 
./nptl/unwind.c:130
+#10 0x00007fa2b4c9dd22 in __do_cancel () at ../sysdeps/nptl/pthreadP.h:271
+#11 __GI___pthread_exit (value=0x0) at ./nptl/pthread_exit.c:36
+#12 0x0000000000000000 in ?? ()
+```
+
+### Root cause
+
+libunwind's `src/unwind/*.c` implements GCC's `_Unwind_*` ABI compatibility 
layer (`_Unwind_GetLanguageSpecificData`, `_Unwind_ForcedUnwind`, 
`_Unwind_Resume`, and so on), which exports the same global symbols as 
`libgcc_s.so.1`. When libunwind is linked as a **shared library** and appears 
before `libgcc_s.so.1` in the final binary's `DT_NEEDED` list, the runtime 
linker `ld.so` resolves `_Unwind_*` calls from `pthread_exit` / exception 
handling to libunwind's DWARF implementation. That i [...]
+
+This is an ELF **runtime symbol-resolution-order** issue. It is independent of 
the compiler (GCC / Clang). Clang's default runtime is also `libstdc++ + 
libgcc_s`, and it hits the same crash.
+
+### Workaround
+
+> **Important: do not use the distro libunwind** (for example `apt install 
libunwind-dev`, `yum install libunwind-devel`). Most distro `libunwind.so` 
builds still export `_Unwind_*` in the dynamic symbol table, which triggers the 
crash in this section.
+>
+> You must use **libunwind built from source**. Upstream `./configure` + 
`make` hides `_Unwind_*` as local via `-Wl,--version-script` by default, so 
they are not exported and the conflict goes away.
+
+Recommended approach per build system:
+
+| Build | Recommendation |
+|---|---|
+| `config_brpc.sh` + `make` | Build and install libunwind from source, then 
pass its include and lib dirs to `config_brpc.sh` |
+| `cmake` | Build and install libunwind from source, then pass its include and 
lib dirs to `cmake` |
+| `bazel` (Bzlmod) | Use the libunwind version maintained in the brpc repo |
+
+### make (config_brpc.sh)
+
+Build and install libunwind into a private prefix (do not pollute the system), 
then point `config_brpc.sh` at that prefix.
+
+```bash
+# 1) Build libunwind from source (v1.8.1 or newer recommended)
+git clone https://github.com/libunwind/libunwind.git
+cd libunwind && git checkout tags/v1.8.1
+mkdir -p /opt/libunwind
+autoreconf -i
+./configure --prefix=/opt/libunwind
+make -j$(nproc) && make install
+cd ..
+
+# 2) Make config_brpc.sh use headers and libs under /opt/libunwind
+#    (do not let it pick up the system libunwind-dev)
+cd brpc
+sh config_brpc.sh \
+    --with-bthread-tracer \
+    --headers="/opt/libunwind/include /usr/include" \
+    --libs="/opt/libunwind/lib /usr/lib /usr/lib64"
+make -j$(nproc)
+```
+
+After the build, confirm `libunwind.so` does not export `_Unwind_*`:
+
+```bash
+nm -D /opt/libunwind/lib/libunwind.so | grep ' T _Unwind_' \
+    && echo "WARN: _Unwind_* exported" \
+    || echo "OK: _Unwind_* hidden"
+```
+
+### cmake
+
+[`CMakeLists.txt`](../../CMakeLists.txt) looks up libunwind with 
`find_library(... NAMES unwind unwind-x86_64)`. Build libunwind from source 
into a private prefix as in the make section, then prefer that prefix with 
`CMAKE_PREFIX_PATH`:
+
+```bash
+# 1) Build libunwind from source (same as the make section)
+
+# 2) Make cmake search headers and libs under /opt/libunwind first
+cd brpc
+mkdir build && cd build
+cmake -DWITH_BTHREAD_TRACER=ON \
+      -DCMAKE_PREFIX_PATH=/opt/libunwind \
+      ..
+make -j$(nproc)
+```
+
+> Tip: if `libunwind-dev` is already installed, `find_library` may still 
prefer `/usr/lib`. Pass
+> `-DLIBUNWIND_LIB=/opt/libunwind/lib/libunwind.so 
-DLIBUNWIND_X86_64_LIB=/opt/libunwind/lib/libunwind-x86_64.so 
-DLIBUNWIND_INCLUDE_PATH=/opt/libunwind/include`
+> to force the self-built copy.
+
+### bazel (Bzlmod)
+
+The brpc repo already maintains a Bzlmod overlay for libunwind under 
[`registry/modules/libunwind/`](../../registry/modules/libunwind/), used via 
`--registry=https://github.com/apache/brpc/registry` in 
[`.bazelrc`](../../.bazelrc). The version uses a `<base>.brpc-no-unwind` suffix 
(for example `1.8.3.brpc-no-unwind`) to distinguish it from the same base 
version on BCR. The overlay adds a switch:
+
+```
+--define libunwind_hide_unwind_symbols=true
+```
+
+When on, libunwind's `src/unwind/*.c` (the GCC `_Unwind_*` compatibility 
layer) is not compiled, matching upstream autoconf's default. bRPC only uses 
libunwind's native `unw_*` API (`unw_getcontext`, `unw_init_local`, `unw_step`, 
and so on) and does not need the `_Unwind_*` layer, so the switch is safe.
+
+`.bazelrc` already turns this on for the `build:test` / `test` configs:
+
+```
+build:test --define libunwind_hide_unwind_symbols=true
+test       --define libunwind_hide_unwind_symbols=true
+```
+
+Add `--define=with_bthread_tracer=true` as in usage step 2:
+
+```bash
+# Tests: the test config in .bazelrc already includes the hide switch
+bazel test //test:bthread_unittest
+
+# Non-test (production) builds need both defines
+bazel build --define=with_bthread_tracer=true \
+            --define=libunwind_hide_unwind_symbols=true \
+            //...
+```
+
+> **Note**: a production build that sets only 
`--define=with_bthread_tracer=true` and omits 
`--define=libunwind_hide_unwind_symbols=true` can crash on `pthread_exit` / 
exception paths.
+
+After the build, confirm the libunwind shared library does not export 
`_Unwind_*`:
+
+```bash
+nm -D bazel-bin/external/_solib_*/libexternal*libunwind*.so 2>/dev/null \
+    | grep ' T _Unwind_' || echo "OK: no _Unwind_* exported by libunwind.so"
+```
+
+# Related flags
+
+- `signal_trace_timeout_ms`: timeout for signal tracing, default 50ms.
diff --git a/docs/en/execution_queue.md b/docs/en/execution_queue.md
index 194251f1..fcd8cd16 100644
--- a/docs/en/execution_queue.md
+++ b/docs/en/execution_queue.md
@@ -1,7 +1,219 @@
-# execution queue
+[中文版](../cn/execution_queue.md)
 
-This document has not yet been translated into English.
+# Overview
 
-Please refer to the [Chinese version](../cn/execution_queue.md) for the full 
content.
+Like kylin's ExecMan, 
[ExecutionQueue](https://github.com/apache/brpc/blob/master/src/bthread/execution_queue.h)
 runs tasks asynchronously and serially. The technique was first used in RPC to 
[write to the same fd from multiple threads](io.md#sending-messages). It was 
added to bthread after r31345. ExecutionQueue provides:
 
-Contributions to translate this document are welcome. See 
[TRANSLATING](TRANSLATING) for guidelines.
+- Async ordered execution: tasks run on a separate thread, strictly in submit 
order.
+- Multi producer: several threads can submit to one ExecutionQueue at the same 
time.
+- Cancel a submitted task
+- Stop
+- High-priority tasks that jump the queue
+
+Main differences from ExecMan:
+
+- Submit is 
[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom). 
ExecMan uses a lock, so when the machine is busy, one process being descheduled 
can block every thread. ExecutionQueue does not.
+- Batching: the consumer can process submitted tasks in batches for better 
locality. After ExecMan finishes one AsyncClient's AsyncContext, the next task 
is often another client's, so the CPU cache keeps bouncing between those 
resources.
+- The execute function is not pinned to a fixed thread. ExecMan hashes 
AsyncClient onto a fixed worker. Different ExecutionQueues are independent; 
with enough threads, every non-idle queue can run at once. With too few 
threads, ExecutionQueue cannot guarantee fairness — then you need to add 
bthread workers to raise overall capacity.
+- The consumer runs as a bthread, so you can use bthread sync primitives 
without blocking a pthread. In ExecMan you should avoid primitives that are 
likely to block.
+
+# Background
+
+In multi-core programming, [message 
passing](https://en.wikipedia.org/wiki/Message_passing) is a common way to 
remove races. Logic is split by the resources it depends on into independent 
actors. Each actor owns its resource. Changing a resource becomes a message to 
that actor. The actor (usually in another context) applies the command, then 
either wakes the caller (sync) or submits to the next actor (async).
+
+![img](http://web.mit.edu/6.005/www/fa14/classes/20-queues-locks/figures/producer-consumer.png)
+
+# ExecutionQueue vs mutex
+
+Both ExecutionQueue and mutex can remove races among threads. Compared with a 
mutex, ExecutionQueue has these advantages:
+
+- Roles are clear, the idea is simple, and you do not have to reason about 
lock problems (such as deadlock).
+- Task order is guaranteed; mutex wakeup order is not.
+- Every thread is doing useful work; nobody waits.
+- Under load or stalls, batching gives higher overall throughput.
+
+The drawbacks are equally real:
+
+- One flow's code is often scattered, so it is harder to read and maintain.
+- To raise concurrency, one job is often pipelined across several 
ExecutionQueues, which hops between cores and pays extra scheduling and 
cache-sync cost. When the critical section is tiny, that cost is not negligible.
+- Operating on several resources atomically gets harder. With mutexes you can 
lock several of them; with ExecutionQueue you need an extra dispatch queue.
+- Everything is single-threaded on that queue, so a slow task blocks every 
other task on the same ExecutionQueue.
+- Flow control is harder. A queue that caches too many tasks can use too much 
memory.
+
+Ignoring performance and complexity, any system can theoretically use only 
mutexes or only ExecutionQueues to remove races. For a complex system, pick per 
scenario:
+
+- If the critical section is tiny and contention is light, prefer a mutex, 
then use the [contention profiler](contention_profiler.md) to see whether it 
becomes a bottleneck.
+- If you need ordered execution, or contention you cannot remove but can batch 
for throughput, choose ExecutionQueue.
+
+There is no universal multi-thread model. Combine profiling with the actual 
workload and balance complexity against performance.
+
+**One extra note**: an uncontended Linux mutex lock/unlock is only a few 
atomic instructions, and the cost is negligible in most cases.
+
+# Usage
+
+### Implement the execute function
+
+```
+// Iterate over the given tasks
+//
+// Example:
+//
+// #include <bthread/execution_queue.h>
+//
+// int demo_execute(void* meta, TaskIterator<T>& iter) {
+//     if (iter.is_queue_stopped()) {
+//         // destroy meta and related resources
+//         return 0;
+//     }
+//     for (; iter; ++iter) {
+//         // do_something(meta, *iter)
+//         // or do_something(meta, iter->a_member_of_T)
+//     }
+//     return 0;
+// }
+template <typename T>
+class TaskIterator;
+```
+
+### Start an ExecutionQueue
+
+```
+struct ExecutionQueueOptions {
+    ExecutionQueueOptions();
+
+    // Execute in resident pthread instead of bthread. default: false.
+    bool use_pthread;
+
+    // Attribute of the bthread which execute runs on. default: 
BTHREAD_ATTR_NORMAL
+    // Bthread will be used when executor = nullptr and use_pthread == false.
+    bthread_attr_t bthread_attr;
+
+    // Executor that tasks run on. default: nullptr
+    // Note that TaskOptions.in_place_if_possible = false will not work, if 
implementation of
+    // Executor is in-place(synchronous).
+    Executor * executor;
+};
+
+// Start a ExecutionQueue. If |options| is nullptr, the queue will be created 
with
+// default options.
+// Returns 0 on success, errno otherwise
+// NOTE: type |T| can be non-POD but must be copy-constructible
+template <typename T>
+int execution_queue_start(
+        ExecutionQueueId<T>* id,
+        const ExecutionQueueOptions* options,
+        int (*execute)(void* meta, TaskIterator<T>& iter),
+        void* meta);
+```
+
+The return value is a 64-bit id, a [weak 
reference](https://en.wikipedia.org/wiki/Weak_reference) to the ExecutionQueue 
instance. You can locate the queue wait-free in O(1). You can copy the id 
freely, even send it in an RPC as a handle to a remote resource.
+You must keep `meta` alive until the ExecutionQueue has really stopped.
+
+### Stop an ExecutionQueue
+
+```
+// Stop the ExecutionQueue.
+// After this function is called:
+//  - All the following calls to execution_queue_execute would fail 
immediately.
+//  - The executor will call |execute| with TaskIterator::is_queue_stopped() 
being
+//    true exactly once when all the pending tasks have been executed, and 
after
+//    this point it's ok to release the resource referenced by |meta|.
+// Returns 0 on success, errno othrwise
+template <typename T>
+int execution_queue_stop(ExecutionQueueId<T> id);
+ 
+// Wait until the the stop task (Iterator::is_queue_stopped() returns true) has
+// been executed
+template <typename T>
+int execution_queue_join(ExecutionQueueId<T> id);
+```
+
+`stop` and `join` can be called more than once and still behave reasonably. 
`stop` can be called at any time without worrying about thread safety.
+
+Like `close` on an fd, if `stop` is never called, the resource leaks forever.
+
+Safe time to free `meta`: when `execute` sees `iter.is_queue_stopped() == 
true`, or after `join` returns. Do not double-free.
+
+### Submit a task
+
+```
+struct TaskOptions {
+    TaskOptions();
+    TaskOptions(bool high_priority, bool in_place_if_possible);
+ 
+    // Executor would execute high-priority tasks in the FIFO order but before
+    // all pending normal-priority tasks.
+    // NOTE: We don't guarantee any kind of real-time as there might be tasks 
still
+    // in process which are uninterruptible.
+    //
+    // Default: false
+    bool high_priority;
+ 
+    // If |in_place_if_possible| is true, execution_queue_execute would call
+    // execute immediately instead of starting a bthread if possible
+    //
+    // Note: Running callbacks in place might cause the dead lock issue, you
+    // should be very careful turning this flag on.
+    //
+    // Default: false
+    bool in_place_if_possible;
+};
+ 
+const static TaskOptions TASK_OPTIONS_NORMAL = TaskOptions(/*high_priority=*/ 
false, /*in_place_if_possible=*/ false);
+const static TaskOptions TASK_OPTIONS_URGENT = TaskOptions(/*high_priority=*/ 
true, /*in_place_if_possible=*/ false);
+const static TaskOptions TASK_OPTIONS_INPLACE = TaskOptions(/*high_priority=*/ 
false, /*in_place_if_possible=*/ true);
+ 
+// Thread-safe and Wait-free.
+// Execute a task with defaut TaskOptions (normal task);
+template <typename T>
+int execution_queue_execute(ExecutionQueueId<T> id,
+                            typename butil::add_const_reference<T>::type task);
+ 
+// Thread-safe and Wait-free.
+// Execute a task with options. e.g
+// bthread::execution_queue_execute(queue, task, &bthread::TASK_OPTIONS_URGENT)
+// If |options| is nullptr, we will use default options (normal task)
+// If |handle| is not nullptr, we will assign it with the handler of this task.
+template <typename T>
+int execution_queue_execute(ExecutionQueueId<T> id,
+                            typename butil::add_const_reference<T>::type task,
+                            const TaskOptions* options);
+template <typename T>
+int execution_queue_execute(ExecutionQueueId<T> id,
+                            typename butil::add_const_reference<T>::type task,
+                            const TaskOptions* options,
+                            TaskHandle* handle);
+                            
+template <typename T>
+int execution_queue_execute(ExecutionQueueId<T> id,
+                            T&& task);
+
+template <typename T>
+int execution_queue_execute(ExecutionQueueId<T> id,
+                            T&& task,
+                            const TaskOptions* options);
+
+template <typename T>
+int execution_queue_execute(ExecutionQueueId<T> id,
+                            T&& task,
+                            const TaskOptions* options,
+                            TaskHandle* handle);
+                            
+```
+
+High-priority tasks also run **strictly in submit order**, unlike ExecMan, 
where `QueueExecEmergent` AsyncContext order is undefined. That also means you 
cannot jump ahead of an already-submitted high-priority task.
+
+`in_place_if_possible` skips one thread schedule and cache sync when there is 
no contention. It can deadlock or recurse too deep (for example endless 
ping-pong). Turn it on only if your code does not have those problems.
+
+### Cancel a submitted task
+
+```
+/// [Thread safe and ABA free] Cancel the corresponding task.
+// Returns:
+//  -1: The task was executed or h is an invalid handle
+//  0: Success
+//  1: The task is executing
+int execution_queue_cancel(const TaskHandle& h);
+```
+
+A non-zero return only means ExecutionQueue has already handed the task to 
`execute`. The real logic may still cache that task in another container, so it 
does not mean the logical task is done. You have to guarantee that in your own 
code.
diff --git a/docs/en/overview.md b/docs/en/overview.md
index 0a704ddc..858daac9 100644
--- a/docs/en/overview.md
+++ b/docs/en/overview.md
@@ -85,7 +85,7 @@ Although almost all RPC implementations claim that they're 
"high-performant", th
 
 * Reading and parsing requests from different clients is fully parallelized 
and users don't need to distinguish between "IO-threads" and 
"Processing-threads". Other implementations probably have "IO-threads" and 
"Processing-threads" and hash file descriptors(fd) into IO-threads. When a 
IO-thread handles one of its fds, other fds in the thread can't be handled. If 
a message is large, other fds are significantly delayed. Although different 
IO-threads run in parallel, you won't have many IO [...]
 * Writing into one fd and multiple fds is highly concurrent. When multiple 
threads write into the same fd (common for multiplexed connections), the first 
thread directly writes in-place and other threads submit their write requests 
in 
[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom) 
manner. One fd can be written into 5,000,000 16-byte messages per second by a 
couple of highly-contended threads. More details can be found 
[here](io.md#sending-messages).
-* Minimal locks. High-QPS services can utilize all CPU power on the machine. 
For example, [creating bthreads](../cn/memory_management.md) for processing 
requests, [setting up timeout](../cn/timer_keeping.md), [finding RPC 
contexts](../cn/bthread_id.md) according to response, [recording performance 
counters](bvar.md) are all highly concurrent. Users see very few contentions 
(via [contention profiler](../cn/contention_profiler.md)) caused by RPC 
framework even if the service runs at 500,000+ QPS.
-* Server adjusts thread number according to load. Traditional implementations 
set number of threads according to latency to avoid limiting the throughput. 
brpc creates a new [bthread](../cn/bthread.md) for each request and ends the 
bthread when the request is done, which automatically adjusts thread number 
according to load.
+* Minimal locks. High-QPS services can utilize all CPU power on the machine. 
For example, [creating bthreads](../cn/memory_management.md) for processing 
requests, [setting up timeout](../cn/timer_keeping.md), [finding RPC 
contexts](bthread_id.md) according to response, [recording performance 
counters](bvar.md) are all highly concurrent. Users see very few contentions 
(via [contention profiler](../cn/contention_profiler.md)) caused by RPC 
framework even if the service runs at 500,000+ QPS.
+* Server adjusts thread number according to load. Traditional implementations 
set number of threads according to latency to avoid limiting the throughput. 
brpc creates a new [bthread](bthread.md) for each request and ends the bthread 
when the request is done, which automatically adjusts thread number according 
to load.
 
 Check [benchmark](../cn/benchmark.md) for a comparison between brpc and other 
implementations.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to