Go与C++线程处理机制差异及高线程数崩溃原因问询
Go与C++线程处理机制差异及高线程场景表现不同的原因
问题背景
在配备8核16逻辑处理器、16GB内存的笔记本上,分别用Go和C++实现线程安全队列,创建超10万个线程向共享队列推送数据:
- Go程序可正常完成队列的读写操作
- C++程序触发段错误(segmentation fault),甚至无法处理5千个线程的场景
Go实现代码及运行结果
Go代码
package main import ( "fmt" "math/rand" "sync" ) type ConcurrentQueue struct { queue []int32 lock sync.Mutex } func (q *ConcurrentQueue) Enqueue(item int32) { q.lock.Lock() defer q.lock.Unlock() q.queue = append(q.queue, item) } func (q *ConcurrentQueue) Dequeue() int32 { q.lock.Lock() defer q.lock.Unlock() if len(q.queue) == 0 { panic("removing from an empty queue") } item := q.queue[0] q.queue = q.queue[1:] return item } func (q *ConcurrentQueue) Size() int { q.lock.Lock() defer q.lock.Unlock() return len(q.queue) } const NUM_THREADS int = 100000 func main() { queue := &ConcurrentQueue{ queue: make([]int32, 0), } var wgE sync.WaitGroup var wgD sync.WaitGroup fmt.Println("size before enqueue:", queue.Size()) for i := 0; i < NUM_THREADS; i++ { wgE.Add(1) go func() { queue.Enqueue(rand.Int31()) wgE.Done() }() } wgE.Wait() fmt.Println("size after enqueue:", queue.Size()) for i := 0; i < NUM_THREADS; i++ { wgD.Add(1) go func() { queue.Dequeue() wgD.Done() }() } wgD.Wait() fmt.Println("size after dequeue:", queue.Size()) }
Go运行输出
size before enqueue: 0 size after enqueue: 100000 size after dequeue: 0
C++实现代码及问题表现
C++代码
#include <iostream> #include <queue> #include <cstdlib> #include <thread> #include <mutex> #include <condition_variable> class ThreadSafeQueue { private: std::queue<int> q; std::mutex qMutex; std::condition_variable m_cond; public: ThreadSafeQueue() { } ThreadSafeQueue(const ThreadSafeQueue &) = delete ; ThreadSafeQueue& operator=(const ThreadSafeQueue &) = delete ; void Enqueue(int val); void Dequeue(); size_t Size(); }; void ThreadSafeQueue::Enqueue(int val) { std::lock_guard<std::mutex> guard(qMutex); q.push(val); } void ThreadSafeQueue::Dequeue() { std::unique_lock<std::mutex> lock(qMutex); m_cond.wait(lock, [this] { return !q.empty(); }); q.pop(); } size_t ThreadSafeQueue::Size() { return q.size(); } void UpdateQueue(ThreadSafeQueue &qObj, int val) { qObj.Enqueue(val); } void DeleteQueue(ThreadSafeQueue &qObj) { qObj.Dequeue(); } int main() { int N; ThreadSafeQueue qObj; std::cout<<"How many values do you want to insert"<<std::endl; std::cin>>N; std::cout<<"Before inserting size of queue is "<<qObj.Size()<<std::endl; std::vector<std::thread> threads; for(int i = 0; i < N; i++) { threads.push_back(std::thread(UpdateQueue, std::ref(qObj), rand())); } for (auto& th : threads) th.join(); threads.clear(); std::cout<<"After inserting size of queue is "<<qObj.Size() <<std::endl; std::cout<<"Dequeuing using threads"<<std::endl; for(int i = 0; i < N; i++) { threads.push_back(std::thread(DeleteQueue, std::ref(qObj))); } for (auto& th : threads) th.join(); std::cout<<"After dequeing size of queue is "<<qObj.Size()<<std::endl; }
C++运行问题
创建10万个线程时直接触发段错误,即使仅创建5千个线程,也会因内存压力过大无法正常运行。
核心差异与原因分析
1. 线程模型:OS线程 vs Goroutine
- C++:
std::thread直接对应操作系统级线程,每个OS线程默认需要几MB的独立栈空间。创建10万个OS线程时,仅栈内存就需要几十GB(按2MB/线程计算,10万*2MB=200GB),远超16GB物理内存上限,直接导致内存耗尽触发段错误。 - Go:使用的Goroutine是用户态轻量级线程,由Go运行时调度。每个Goroutine初始栈仅几KB,且可动态扩容缩容,10万个Goroutine的栈内存总计仅几百MB,完全在16GB内存的承载范围内。
2. 调度机制:内核调度 vs 用户态调度
- C++:OS线程由内核调度器管理,线程切换需要完成上下文保存、寄存器刷新、TLB更新等操作,开销极高。大量线程会导致调度资源耗尽,同时内存压力进一步加剧系统崩溃。
- Go:Go运行时采用M:N调度器,将多个Goroutine映射到少量OS线程(通常与CPU核心数匹配),Goroutine的切换在用户态完成,切换成本极低。即使10万个Goroutine,也只会由少数OS线程承载,避免了内核级调度的高开销和内存爆炸问题。
3. C++代码的额外隐患
C++代码中的Size()方法未加锁,当其他线程正在修改队列时调用size()会引发数据竞争,可能返回错误的队列长度,但这不是崩溃的核心原因。崩溃的根本原因还是OS线程的内存占用过高。
内容的提问来源于stack exchange,提问作者Himanshu Poddar
相关产品推荐
相关产品推荐

