并发编程指南 同步操作与强制排序
文章目录
- 5.3 同步操作与强制排序
- 代码5.2 多线程数据读写示例
- 5.3.1 同步发生
- 5.3.2 先行发生
- 5.3.3 原子操作的内存序
- 顺序一致性序
- 自由序
- 获取-释放序
- 5.3.4 释放序列与同步
- 5.3.5 栅栏
- 5.3.6 原子操作对非原子操作排序
- 5.3.7 非原子操作排序
5.3 同步操作与强制排序
在多线程编程中,当多个线程同时访问共享数据时,需要谨慎处理同步问题。让我们通过一个简单例子来理解这个概念:假设一个线程向数据结构写入数据,另一个线程从中读取数据。为了避免数据竞争,写入线程会设置一个标志位表示数据已准备就绪,读取线程则需等待该标志位被设置后才能读取数据。
代码5.2 多线程数据读写示例
#include <vector>
#include <atomic>
#include <iostream>
#include <thread>std::vector<int> data;
std::atomic<bool> data_ready(false);void reader_thread()
{while(!data_ready.load()) // 1. 等待数据准备就绪{std::this_thread::sleep(std::chrono::milliseconds(1));}std::cout << "The answer=" << data[0] << "\n"; // 2. 读取数据
}void writer_thread()
{data.push_back(42); // 3. 写入数据data_ready = true; // 4. 设置数据就绪标志
}int main()
{std::thread writer(writer_thread);std::thread reader(reader_thread);writer.join();reader.join();return 0;
}
在这个例子中,虽然等待循环①本身是原子的,但非原子读取操作②和写入操作③如果无序执行,就会产生未定义行为。我们通过原子变量data_ready
的操作来建立执行顺序:数据写入③必须先于标志设置④,标志检查①必须先于数据读取②。当data_ready
为true时,写操作与读操作同步,建立了"先行"关系。
5.3.1 同步发生
"同步发生"关系只在原子类型操作间存在。当线程A执行原子写操作,线程B执行原子读操作且读取的是A写入的值(或之后写入的值),那么A的写操作与B的读操作就是同步发生关系。
5.3.2 先行发生
"先行发生"关系是程序操作顺序的基本构建块。在单线程中,如果操作A在操作B之前执行,那么A就先行于B。在多线程环境中,如果操作A与另一线程上的操作B同步发生,那么A线程间先行于B。
5.3.3 原子操作的内存序
C++提供了六种内存序选项:
memory_order_relaxed
- 自由序memory_order_consume
- 消费序(C++17中不推荐使用)memory_order_acquire
- 获取序memory_order_release
- 释放序memory_order_acq_rel
- 获取-释放序memory_order_seq_cst
- 顺序一致性序(默认)
顺序一致性序
顺序一致性是最严格的内存序,保证所有线程看到的操作顺序一致。下面是顺序一致性的示例:
#include <atomic>
#include <thread>
#include <assert.h>std::atomic<bool> x, y;
std::atomic<int> z;void write_x() { x.store(true, std::memory_order_seq_cst); }
void write_y() { y.store(true, std::memory_order_seq_cst); }void read_x_then_y()
{while(!x.load(std::memory_order_seq_cst));if(y.load(std::memory_order_seq_cst)) ++z;
}void read_y_then_x()
{while(!y.load(std::memory_order_seq_cst));if(x.load(std::memory_order_seq_cst)) ++z;
}int main()
{x = false;y = false;z = 0;std::thread a(write_x);std::thread b(write_y);std::thread c(read_x_then_y);std::thread d(read_y_then_x);a.join();b.join();c.join();d.join();assert(z.load() != 0); // 永远不会触发
}
自由序
自由序只保证原子操作的原子性,不提供任何顺序保证:
#include <atomic>
#include <thread>
#include <assert.h>std::atomic<bool> x, y;
std::atomic<int> z;void write_x_then_y()
{x.store(true, std::memory_order_relaxed); // 1y.store(true, std::memory_order_relaxed); // 2
}void read_y_then_x()
{while(!y.load(std::memory_order_relaxed)); // 3if(x.load(std::memory_order_relaxed)) ++z; // 4
}int main()
{x = false;y = false;z = 0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load() != 0); // 可能触发!
}
获取-释放序
获取-释放序提供了比自由序更强的同步保证:
#include <atomic>
#include <thread>
#include <assert.h>std::atomic<bool> x, y;
std::atomic<int> z;void write_x_then_y()
{x.store(true, std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_release); // 释放栅栏y.store(true, std::memory_order_relaxed);
}void read_y_then_x()
{while(!y.load(std::memory_order_relaxed));std::atomic_thread_fence(std::memory_order_acquire); // 获取栅栏if(x.load(std::memory_order_relaxed)) ++z;
}int main()
{x = false;y = false;z = 0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load() != 0); // 不会触发
}
5.3.4 释放序列与同步
释放序列确保了一系列原子操作的正确同步:
#include <atomic>
#include <thread>
#include <vector>std::vector<int> queue_data;
std::atomic<int> count;void populate_queue()
{unsigned const number_of_items = 20;queue_data.clear();for(unsigned i = 0; i < number_of_items; ++i){queue_data.push_back(i);}count.store(number_of_items, std::memory_order_release);
}void process(int item) { /* 处理数据 */ }void consume_queue_items()
{while(true){int item_index;if((item_index = count.fetch_sub(1, std::memory_order_acquire)) <= 0){continue; // 等待更多项目}process(queue_data[item_index - 1]);}
}int main()
{std::thread a(populate_queue);std::thread b(consume_queue_items);std::thread c(consume_queue_items);a.join();b.join();c.join();
}
5.3.5 栅栏
内存栅栏提供了对内存操作顺序的强制约束:
#include <atomic>
#include <thread>
#include <assert.h>std::atomic<bool> x, y;
std::atomic<int> z;void write_x_then_y()
{x.store(true, std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_release);y.store(true, std::memory_order_relaxed);
}void read_y_then_x()
{while(!y.load(std::memory_order_relaxed));std::atomic_thread_fence(std::memory_order_acquire);if(x.load(std::memory_order_relaxed)) ++z;
}int main()
{x = false;y = false;z = 0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load() != 0); // 不会触发
}
5.3.6 原子操作对非原子操作排序
原子操作也可以对非原子操作进行排序:
#include <atomic>
#include <thread>
#include <assert.h>bool x = false; // 非原子变量
std::atomic<bool> y;
std::atomic<int> z;void write_x_then_y()
{x = true; // 非原子写入std::atomic_thread_fence(std::memory_order_release);y.store(true, std::memory_order_relaxed);
}void read_y_then_x()
{while(!y.load(std::memory_order_relaxed));std::atomic_thread_fence(std::memory_order_acquire);if(x) ++z; // 读取非原子变量
}int main()
{y = false;z = 0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load() != 0); // 不会触发
}
5.3.7 非原子操作排序
非原子操作可以通过原子操作进行排序,这是更高级同步工具的基础:
#include <atomic>
#include <thread>class spinlock_mutex
{std::atomic_flag flag;
public:spinlock_mutex() : flag(ATOMIC_FLAG_INIT) {}void lock(){while(flag.test_and_set(std::memory_order_acquire));}void unlock(){flag.clear(std::memory_order_release);}
};spinlock_mutex mutex;
int shared_data = 0;void worker()
{mutex.lock();++shared_data; // 受保护的操作mutex.unlock();
}int main()
{std::thread t1(worker);std::thread t2(worker);t1.join();t2.join();return 0;
}
C++标准库提供了多种同步机制,包括互斥量、条件变量、future等,它们都基于这些基本的内存序概念构建,为多线程编程提供了更高级的抽象。
理解这些内存序概念对于编写正确高效的多线程程序至关重要。在实际开发中,应该优先使用高级同步工具,只有在需要极致性能时才考虑直接使用原子操作和内存序。