加锁是一种悲观的策略,它总是认为每次访问共享资源的时候总会发生冲突。
无锁是一种乐观的策略,它假设线程访问共享资源不会发生冲突,所以不需要加锁。
1 CAS
核心思想
无锁的策略使用一种比较交换的技术(CAS Compare And Swap)来鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突为止。
CAS是系统原语,CAS操作是一条CPU的原子指令,所以不会有线程安全问题。
template <class T>
bool CAS(T expected, T value)
{
if (*addr == expected)
{
*addr = value;
return true;
}
return false;
}
- 读取内存地址addr的当前值,将其与期望值expected进行比较。
- 如果当前值等于期望值,则将内存地址addr的值更新为新值value。
- 如果当前值不等于期望值,则不进行任何操作。
- 当前大多数机器都在硬件级实现了这个操作,在 Inter 处理器上这个操作是 CMPXCHG ,因而 CAS 是一个最基础的原子操作。
2 手撸一个无锁队列
- 头文件
//
// Created by AlbertZ on 2023/6/8.
//
#ifndef NO_LOCK_QUEUE_MY_LOCK_FREE_QUEUE_H
#define NO_LOCK_QUEUE_MY_LOCK_FREE_QUEUE_H
#include <atomic>
#include <iostream>
template<typename T,size_t size>
class MyLockFreeQueue{
static_assert(std::is_trivial<T>::value, "The type T must be trivial");
static_assert(size > 2, "Buffer size must be bigger than 2");
public:
MyLockFreeQueue();
~MyLockFreeQueue();
bool push(const T& data);
bool pop(T& data);
private:
T* _data = nullptr;
std::atomic<int> _head;
std::atomic<int> _tail;
};
// 构造函数
template <typename T, size_t size>
MyLockFreeQueue<T, size>::MyLockFreeQueue() : _head(0), _tail(0) {
_data = new T[size];
}
// 析构函数
template <typename T, size_t size>
MyLockFreeQueue<T, size>::~MyLockFreeQueue(){
delete[] _data;
}
// push
template<typename T, size_t size>
bool MyLockFreeQueue<T,size>::push(const T& data){
const size_t w = _tail.load(std::memory_order_relaxed);
size_t w_next = w + 1;
if (w_next == size) {
w_next = 0U;
}
/* Full check */
const size_t r = _head.load(std::memory_order_acquire);
if (w_next == r) {
return false;
}
/* Place the element */
_data[w] = data;
/* Store the next write index */
_tail.store(w_next, std::memory_order_release);
return true;
}
// pop
template <typename T, size_t size>
bool MyLockFreeQueue<T, size>::pop(T& data){
size_t r = _head.load(std::memory_order_relaxed);
const size_t w = _tail.load(std::memory_order_acquire);
/* Empty check */
if (r == w) {
return false;
}
/* Remove the element */
data = _data[r];
/* Increment the read index */
r++;
if (r == size) {
r = 0U;
}
/* Store the read index */
_head.store(r, std::memory_order_release);
return true;
}
#endif //NO_LOCK_QUEUE_MY_LOCK_FREE_QUEUE_H
- 测试函数
//
// Created by AlbertZ on 2023/6/8.
//
#include <iostream>
#include <thread>
#include <future>
#include "my_lock_free_queue.h"
MyLockFreeQueue<int , 50> queue;
void PushThread() {
for (int i = 0; i < 100; ++i) {
queue.push(i); // 将数字 1 到 20 依次加入队列
// std::cout << "Push " << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void PopThread() {
for(int i = 0; i < 100; i++) {
int value;
if (queue.pop(value)) {
std::cout << std::this_thread::get_id() << " Popped: " << value << std::endl; // 输出弹出的值
} else {
std::cout << "Queue is empty" << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
int main() {
auto future_push = std::async(std::launch::async,PushThread);
auto future_pop1 = std::async(std::launch::async,PopThread);
auto future_pop2 = std::async(std::launch::async,PopThread);
std::cout << "Hello async!" << std::endl;
std::cin.get();
return 0;
}