多线程是否真的有必要?

文章讨论了多线程在实际项目中的必要性,通过一个例子展示了优化代码性能的重要性,包括提高Cache命中率和减少分支语句。作者提出,虽然多线程能更好地利用CPU资源,但涉及数据共享时可能并不高效,并提醒面试官和开发者深入理解多线程的复杂性。

一点疑问

       相比大家在投简历、面试等等过程中,或多或少会遇到这么一个问题:熟悉掌握多线程开发;谈谈你对多线程的认识。

       其实,我有这么一个疑问,那就是多线程真的有必要么?根据我这两年来的项目经验,也或多或少用了一些多线程的东西,其中有的失败了,有的成功了。但是根据我的所知,有很多程序虽然计算规模很大,实际上却没有使用多线程,但是速度依然很快。有很多程序就算用了多线程,也不见得快了多少。

       那么,多线程真的有必要么?

 

一个例子

       由于我对多线程的了解也是非常浅薄,不敢断言说“必要”或者“不必要”。由于暂定学习多线程定在第三季度或者第四季度,所以仅发表一些个人的观点。

       曾经看过这么一段代码,从中来浅析一下一个程序性能瓶颈是否在于“多线程”。例子如下:

int main(void)
{
   const size_t arraySize = 30000;
   int data[arraySize];
   for (size_t c = 0; c < arraySize; ++c)
    {
       data[c] = std::rand() % 256;
    }
 
   clock_t start = clock();
   long long sum = 0;
 
   for (size_t i = 0; i < 100000; ++i)
    {
       for (size_t c = 0; c < arraySize; ++c)
       {
           if (data[c] >= 128)
           {
                sum += data[c];
           }
       }
    }
 
   double elapsedTime = static_cast<double>(clock() - start) /CLOCKS_PER_SEC;
   std::cout << elapsedTime << std::endl;
   std::cout << "sum = " << sum << std::endl;
 
   return 0;
}

       考虑这个程序,简单说就是从3万个随机数中找到大于等于128的进行累加,这个过程循环10万次,在Visual Studio 2013,Win32平台Release下执行时间为:17s。

那么可以进行优化么?乍看起来,好像无从下手,这么简单的代码还需要进行优化吗?

 

第一种优化

       既然提

<think>我们正在讨论C++多线程编程中的问题及同步手段。根据引用内容,多线程并发访问共享数据会导致数据竞争和不一致。例如,引用[2]中的代码展示了两个线程同时递增一个全局变量`i`,由于未加锁,最终结果往往小于预期值(200000)。引用[3]也强调,当多个线程读写同一内存时,必须同步以保证数据一致性。 ### 一、C++多线程编程中的常见问题 1. **数据竞争(Data Race)** 当多个线程**无同步**地访问同一共享数据且至少有一个是写操作时发生。例如: ```cpp int counter = 0; // 共享数据 void increment() { for (int i = 0; i < 100000; ++i) { ++counter; // 非原子操作 } } ``` 两个线程同时执行`increment()`后,`counter`的值通常小于200000[^2]。 2. **死锁(Deadlock)** 多个线程互相等待对方释放资源,导致永久阻塞。常见于**多个互斥量**使用不当: ```cpp // 线程1 mutex1.lock(); mutex2.lock(); // 若此时线程2已锁mutex2,则阻塞 // ... mutex2.unlock(); mutex1.unlock(); // 线程2(相反顺序) mutex2.lock(); mutex1.lock(); // 若线程1已锁mutex1,则阻塞 ``` 结果:两个线程无限等待[^3]。 3. **活锁(Livelock)** 线程持续尝试解决冲突但无法推进。例如两个线程同时检测到死锁并回退,但回退后再次执行相同操作。 4. **优先级反转(Priority Inversion)** 低优先级线程持有高优先级线程所需资源,导致高优先级线程被阻塞。 ### 二、线程同步的核心手段 #### 1. 互斥锁(Mutex) - **作用**:确保同一时间只有一个线程访问共享资源。 - **C++实现**:`std::mutex`(需手动加锁/解锁)或`std::lock_guard`(RAII自动管理): ```cpp #include <mutex> std::mutex mtx; int shared_data = 0; void safe_increment() { std::lock_guard<std::mutex> lock(mtx); // 构造时加锁,析构时解锁 ++shared_data; } ``` #### 2. 条件变量(Condition Variable) - **作用**:允许线程阻塞等待特定条件成立(需配合互斥锁使用)。 - **典型场景**:生产者-消费者模型: ```cpp std::mutex mtx; std::condition_variable cv; queue<int> data_queue; bool ready = false; void producer() { std::lock_guard<std::mutex> lock(mtx); data_queue.push(42); ready = true; cv.notify_one(); // 通知等待的消费者 } void consumer() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return ready; }); // 阻塞直到ready为true int data = data_queue.front(); data_queue.pop(); } ``` #### 3. 原子操作(Atomic Operations) - **作用**:通过硬件指令保证操作的原子性,无需锁。 - **C++实现**:`std::atomic`模板类: ```cpp #include <atomic> std::atomic<int> counter(0); // 原子计数器 void atomic_increment() { for (int i = 0; i < 100000; ++i) { counter.fetch_add(1, std::memory_order_relaxed); // 原子递增 } } // 结果counter==200000(无数据竞争) ``` #### 4. 读写锁(Read-Write Lock) - **作用**:允许多个读线程同时访问,但写线程独占资源。 - **C++17+**:`std::shared_mutex`: ```cpp #include <shared_mutex> std::shared_mutex rw_mutex; int data = 0; void reader() { std::shared_lock lock(rw_mutex); // 共享锁(读锁) std::cout << data; // 允许多个读者同时访问 } void writer() { std::unique_lock lock(rw_mutex); // 独占锁(写锁) data = 100; // 写时禁止其他读写 } ``` ### 三、同步手段选择建议 | **场景** | **推荐同步方式** | |--------------------------|-----------------------| | 简单计数器递增 | `std::atomic` | | 临界区代码复杂 | `std::mutex` | | 等待外部条件(如IO完成) | `std::condition_variable` | | 读多写少 | `std::shared_mutex` | > **关键原则**:尽可能缩小锁的作用范围(减少锁粒度),避免死锁(按固定顺序获取锁)[^3][^4]。 §§相关问题§§ 1. C++中`std::atomic`支持哪些内存顺序选项?有何区别? 2. 如何用C++实现一个线程安全的单例模式? 3. 死锁的四个必要条件是什么?如何预防? 4. C++20引入了哪些新的并发编程特性?</think>### C++多线程编程中的问题及线程同步手段 #### 一、多线程编程中的常见问题 1. **数据竞争(Data Race)** 当多个线程**无同步**地访问同一共享数据且至少有一个是写操作时发生,导致未定义行为[^3]。 ```cpp int counter = 0; // 共享数据 void increment() { for (int i = 0; i < 100000; ++i) { ++counter; // 非原子操作 } } // 两个线程同时调用increment()后,counter通常小于200000 ``` 2. **死锁(Deadlock)** 多个线程互相等待对方释放资源,导致永久阻塞[^3]: ```cpp std::mutex mtx1, mtx2; // 线程1 mtx1.lock(); mtx2.lock(); // 若此时线程2已锁mtx2,则阻塞 // ... mtx2.unlock(); mtx1.unlock(); // 线程2(相反顺序) mtx2.lock(); mtx1.lock(); // 若线程1已锁mtx1,则阻塞 ``` 3. **活锁(Livelock)** 线程持续尝试解决冲突但无法推进(如两个线程同时检测到死锁并回退,但回退后再次执行相同操作)。 4. **优先级反转(Priority Inversion)** 低优先级线程持有高优先级线程所需资源,导致高优先级线程被阻塞。 5. **缓存一致性问题** 多核CPU中,不同核心的缓存数据不一致(需内存屏障解决)[^4]。 --- #### 二、常用线程同步手段 1. **互斥锁(Mutex)** 确保同一时间只有一个线程访问共享资源: ```cpp #include <mutex> std::mutex mtx; void safe_access() { std::lock_guard<std::mutex> lock(mtx); // RAII自动解锁 // 访问共享资源 } ``` 2. **条件变量(Condition Variable)** 实现线程间事件通知机制(需配合互斥锁): ```cpp std::condition_variable cv; std::mutex mtx; bool data_ready = false; // 生产者 { std::lock_guard lock(mtx); data_ready = true; cv.notify_one(); // 通知消费者 } // 消费者 { std::unique_lock lock(mtx); cv.wait(lock, []{ return data_ready; }); // 阻塞直到条件满足 } ``` 3. **原子操作(Atomic)** 通过硬件指令保证操作的原子性(无锁同步): ```cpp #include <atomic> std::atomic<int> counter(0); void safe_increment() { counter.fetch_add(1, std::memory_order_relaxed); } ``` 4. **读写锁(Shared Mutex)** 允许多个读线程同时访问(C++17起): ```cpp #include <shared_mutex> std::shared_mutex rw_mutex; // 读操作 { std::shared_lock lock(rw_mutex); // 共享锁 // 读取数据 } // 写操作 { std::unique_lock lock(rw_mutex); // 独占锁 // 修改数据 } ``` 5. **信号量(Semaphore)** 控制同时访问资源的线程数量(C++20引入): ```cpp #include <semaphore> std::counting_semaphore<10> sem(3); // 允许3个线程同时访问 void access_resource() { sem.acquire(); // 获取信号量 // 访问资源 sem.release(); // 释放信号量 } ``` --- #### 三、同步手段选择建议 | **场景** | **推荐同步方式** | |--------------------------|-----------------------| | 简单计数器递增 | `std::atomic` | | 临界区代码复杂 | `std::mutex` | | 等待外部条件(如IO完成) | `std::condition_variable` | | 读多写少 | `std::shared_mutex` | > **关键原则**: > 1. 优先使用原子操作或无锁数据结构 > 2. 缩小锁的作用范围(减少锁粒度) > 3. 避免嵌套锁预防死锁[^2] > 4. 使用RAII管理锁资源(如`lock_guard`)
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值