目录
1. 什么是无锁编程
无锁编程(Lock-Free Programming)是并发编程的一种重要技术,它避免了传统的锁机制(例如互斥锁、自旋锁等),并通过院子操作的方式确保线程的安全。无锁编程能够在多线程环境中显著提升程序的并发性和可伸缩性,减少线程之间的竞争、阻塞和上下文切换开销。
1.1 特性
无锁编程和核心目标是减少多线程并发执行时间的竞争和同步开销。详细如下:
- 提高并发性: 减少线程等待和阻塞的时间,提高程序的并发执行能力;
- 避免死锁: 传统的锁机制(互斥锁、自旋锁等)可能会导致死锁,影响程序稳定性。无锁编程避免了死锁问题;
- 减少上下文切换开销: 互斥锁会导致线程阻塞,从而触发上下文切换,而无锁编程可以减少这总开销;
- 降低延迟: 无锁编程能降低线程在共享资源上的竞争,提供响应速度。
1.2 原理
无锁编程,其实是依赖原子操作来确保线程的安全性,而不是使用锁。原子操作是在并发环境中不可分割的操作,确保了操作的完整性。常见的院子操作有:
原子加减法: 对共享变量进行加法或减法操作,保证在多个线程并发执行时,操作是不可分割。
CAS(Compare and Swap,比较并交换): CAS是无锁编程的核心原子操作之一,主要用于条件性更新数据。它会比较内存中的值和期望值,如果相等则更新,否则不做操作。
原子读写: 读取和写入值属于原子操作,确保并发操作的正确性。
利用上面的原子操作,线程可以再不适用锁的情况下,对共享的数据进行修改和同步,从而避免了锁带来的性能瓶颈。
1.3 应用场景
无锁编程广泛用于高并发、低延迟、低开销的系统中,那么它都有哪些应用场景呢?
1.3.1 高性能队列和栈
无锁队列和栈广泛应用于任务队列、消息队列等并发场景,它们能够确保多个线程并能够并发地操作队列和栈,且不需要加锁。
1.3.2 并发计数器
无锁计数器可以高效地实现多线程环境下的计数操作,避免线程之间的竞争和阻塞。
std::atomic<int> a;
a.fetch_add(1);
1.3.3 线程池
无锁线程池有效地调度和管理线程,减少线程间的阻塞,提高任务分发的效率。
1.3.4 内存管理
无锁内存分配器和垃圾回收器能够高效管理内存,避免锁操作带来的性能瓶颈。常见的无锁内存分配器如TLSF(Two-Level Segregate Fit)。
1.3.5 高并发数据结构
无锁编程用于构建高效的并发数据结构,如无锁哈希表、链表、树等。这些数据结构能够在多线程环境下并发操作,提升系统的吞吐量。
2. 无锁编程的实现
2.1 无锁队列
无锁队列,使用std::atomic进行实现,如下所示:
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <chrono>
template<typename T>
class LockFreeQueue{
public:
struct Node{
std::atomic<Node*> next;
T value;
Node(T obj): next(nullptr), value(obj){}
Node(): next(nullptr), value(0){}
};
public:
LockFreeQueue(){
Node* node = new Node(T());
head.store(node);
tail.store(node);
}
~LockFreeQueue(){
while (Node* current = head.load()) {
Node* next = current->next.load();
delete current;
head.store(next);
}
}
void enqueue(T obj){
Node* node = new Node(obj);
auto current = tail.load();
// CAS operation
while(!tail.compare_exchange_weak(current, node)){
current = tail.load();
}
// insert node to queue
current->next.store(node);
}
bool dequeue(T& value){
Node* current = head.load();
Node* next = current->next.load();
if(nullptr == next){
return false;
}
// CAS operation
value = next->value;
if (current == head.load()){
while(!head.compare_exchange_weak(current, next)){
current = head.load();
next = current->next.load();
break;
}
}
head.store(next);
delete current;
return true;
}
private:
std::atomic<Node*> head;
std::atomic<Node*> tail;
};
无锁队列的测试代码(包含单线程、多线程的测试),如下所示:
int main(int argc, char** argv){
LockFreeQueue<int> queue;
int result;
auto enqueue_fun = [&](int value){
queue.enqueue(value);
};
auto dequeue_fun = [&](){
int value;
if (queue.dequeue(value)){
std::cout << "value: " << value << std::endl;
}
};
std::cout << "1. Single thread" << std::endl;
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
while(queue.dequeue(result)){
std::cout << "result: " << result << std::endl;
}
std::cout << "\n\n\n" << std::endl;
std::cout << "************************************************************************" << std::endl;
std::cout << "2. Multi thread enqueue" << std::endl;
std::vector<std::thread> threads;
for (int i =1; i < 10; ++i){
threads.push_back(std::thread(enqueue_fun, i));
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
while(queue.dequeue(result)){
std::cout << "result: " << result << std::endl;
}
for(auto& th: threads){
if (th.joinable()){
th.join();
}
}
threads.clear();
std::cout << "\n\n\n" << std::endl;
std::cout << "************************************************************************" << std::endl;
std::cout << "3. Multi thread dequeue" << std::endl;
std::vector<std::thread> dethreads;
for (int i =1; i < 10; i++){
queue.enqueue(i);
}
for (int i =1; i < 10; ++i){
dethreads.push_back(std::thread(dequeue_fun));
}
for(auto& th: dethreads){
if (th.joinable()){
th.join();
}
}
dethreads.clear();
return 0;
}
2.2 无锁循环buffer
无锁循环buffer,使用std::atomic进行实现,如下所示
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>
#include <chrono>
#include <string.h>
#define BUFFER_SIZE (128)
template<typename T>
class LockFreeBuffer{
public:
struct CircularBuffer{
T buffer[BUFFER_SIZE];
std::atomic_size_t read_index;
std::atomic_size_t write_index;
};
public:
LockFreeBuffer(){
memset(buffer.buffer, 0, BUFFER_SIZE * sizeof(T));
buffer.read_index.store(0);
buffer.write_index.store(0);
}
~LockFreeBuffer(){
memset(buffer.buffer, 0, BUFFER_SIZE * sizeof(T));
buffer.read_index.store(0);
buffer.write_index.store(0);
}
bool reset(){
memset(buffer.buffer, 0, BUFFER_SIZE * sizeof(T));
buffer.read_index.store(0);
buffer.write_index.store(0);
return true;
}
size_t length(){
if (buffer.read_index.load() < buffer.write_index.load()){
return buffer.write_index.load() - buffer.read_index.load();
} else if (buffer.read_index.load() > buffer.write_index.load()) {
return (BUFFER_SIZE - buffer.read_index.load() + buffer.write_index.load());
} else {
return 0;
}
}
bool enqueue(T data){
while(true){
size_t write_pos = buffer.write_index.load();
size_t next_pos = (write_pos + 1) % BUFFER_SIZE;
if(next_pos == buffer.read_index.load()) {
return false;
}
if (buffer.write_index.compare_exchange_weak(write_pos, next_pos)){
buffer.buffer[write_pos] = data;
return true;
}
}
}
bool dequeue(T& value){
while(true){
size_t read_pos = buffer.read_index.load();
// std::cout << read_pos << ":" << buffer.write_index.load() << std::endl;
if (read_pos == buffer.write_index.load()){
return false;
}
value = buffer.buffer[read_pos];
if (buffer.read_index.compare_exchange_weak(read_pos, (read_pos + 1)%BUFFER_SIZE)){
return true;
}
}
}
private:
CircularBuffer buffer;
}; // class LockFreeBuffer
无锁循环buffer的测试代码(包含单线程、多线程的测试),如下所示:
int main(int argc, char** argv){
LockFreeBuffer<int> buffer;
int result;
auto enqueue_fun = [&](int data){
if (!buffer.enqueue(data)){
std::cout << "enqueue buffer failed: " << data << std::endl;
}
};
auto dequeue_fun = [&](){
int data;
if (buffer.dequeue(data)){
std::cout << "result: " << data << std::endl;
}
};
std::cout << "1. Single thread" << std::endl;
for(int i = 0; i < 10; i++){
if (!buffer.enqueue(i)){
std::cout << "enqueue buffer failed: " << i << std::endl;
}
}
while(buffer.dequeue(result)){
std::cout << "result: " << result << std::endl;
}
std::cout << "\n\n\n" << std::endl;
std::cout << "************************************************************************" << std::endl;
std::cout << "2. Multi thread enqueue" << std::endl;
buffer.reset();
std::vector<std::thread> threads_v1;
for(int i = 0; i < 10; i++){
threads_v1.push_back(std::thread(enqueue_fun, i));
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "buffer size:" << buffer.length() << std::endl;
while(buffer.dequeue(result)){
std::cout << "result: " << result << std::endl;
}
for(auto& th: threads_v1){
if (th.joinable()){
th.join();
}
}
threads_v1.clear();
std::cout << "\n\n\n" << std::endl;
std::cout << "************************************************************************" << std::endl;
std::cout << "3. Multi thread dequeue" << std::endl;
buffer.reset();
for(int i = 0; i < 10; i++){
if (!buffer.enqueue(i)){
std::cout << "enqueue buffer failed: " << i << std::endl;
}
}
std::vector<std::thread> threads_v2;
for(int i = 0; i < 10; i++){
threads_v2.push_back(std::thread(dequeue_fun));
}
for(auto& th: threads_v2){
if (th.joinable()){
th.join();
}
}
threads_v2.clear();
std::cout << "\n\n\n" << std::endl;
std::cout << "************************************************************************" << std::endl;
std::cout << "4. Multi thread dequeue & enqueue" << std::endl;
std::vector<std::thread> threads_v3;
for(int i = 0; i < 10; i++){
threads_v3.push_back(std::thread(enqueue_fun, i));
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "buffer size:" << buffer.length() << std::endl;
for(int i = 0; i < 10; i++){
threads_v3.push_back(std::thread(dequeue_fun));
}
for(auto& th: threads_v3){
if (th.joinable()){
th.join();
}
}
threads_v3.clear();
return 0;
}
3. 无锁编程的不足之处
无锁编程能显著提高并发的性能,但是它也有不足之处,会带来新的挑战:
3.1 ABA问题
ABA问题是在并发编程中常见的一个问题,特别是在使用无锁数据结构(如CAS,Compare-And-Swap)时。ABA问题指的是一个变量的值从A变为B,然后又变回A,虽然最终值仍然是A,但在这期间可能发生了其他操作,导致程序逻辑出错。
// ABA问题示例
struct Node {
int value;
Node* next;
};
bool CAS(Node** ptr, Node* old_value, Node* new_value) {
// 可能发生ABA问题
return atomic_compare_exchange_strong(ptr, &old_value, new_value);
}
在 CAS 操作中,ABA 问题指的是一个值在 CAS 检查和更新之间发生了变化,最终 CAS 仍然会认为值没有改变。为了解决 ABA 问题,通常可以通过引入版本号或者使用指针标记技术来标识值的变化。
// 引入版本号的方式
#include <stdint.h>
#include <stdatomic.h>
#include <stdio.h>
typedef struct {
void* ptr;
uint16_t version;
} ABAQuestion;
ABAQuestion global_ptr = { NULL, 0 };
void* atomic_update(void* new_ptr) {
ABAQuestion old_val, new_val;
do {
old_val = global_ptr;
new_val.ptr = new_ptr;
new_val.version = old_val.version + 1;
} while (!atomic_compare_exchange_strong(&global_ptr, &old_val, new_val));
return old_val.ptr;
}
int main() {
int x = 10;
atomic_update(&x);
printf("Updated pointer with new version\n");
return 0;
}
3.2 设计复杂性
无锁编程的设计和实现比传统锁算法复杂得多。编写无锁数据结构和算法需要对并发性、内存模型、原子操作等有深入的理解。特别是在多核或多处理器的环境中,内存一致性和原子性问题更加复杂。
此外,由于无锁编程的并发行为是难以预测的,其调试难度和验证无锁程序的正确性可见是非常困难的。竞争条件、ABA问题等并发问题可能难以复现和诊断。
3.3 可扩展性限制
扩展性限制主要由两个方面,即硬件限制和算法限制:
硬件限制,无锁编程的性能优势依赖于硬件的支持。在某些硬件架构上,原子操作的开销可能较高,导致无锁编程的性能优势不明显。
算法限制,并非所有的并发算法都适合无锁编程。某些算法可能无法有效地转换为无锁形式,或者无锁实现会导致性能下降。