6.C++死锁问题如何分析调试-c++ linux编程:从0实现muduo库系列

重点内容

视频讲解:《C++Linux编程进阶:从0实现muduo C++网络框架系列》-第6讲.C++死锁问题如何分析调试-原子操作,互斥量,条件变量的封装

代码改动

lesson6代码

  • 实现:base/Atomic.h
  • 实现:base/Mutex.h
  • 实现:base/Condition.h/cc
  • examples/test_atomic_mutex.cc

1 AtomicIntegerT原子操作封装

1.1 封装意义

目的是更符号项目的用法,用起来更得心应手。

这种封装在网络库中特别有用,因为网络库经常需要处理并发场景,比如:

  • 统计连接数
  • 管理连接状态
  • 处理引用计数
  • 实现无锁队列
  • 实现线程安全的计数器

1.2 类图

AtomicIntegerT<T>
├── 私有成员
│   └── volatile T value_
├── 构造/析构
│   ├── AtomicIntegerT()	// 默认构造函数,初始值为0
│   └── explicit AtomicIntegerT(T x)	// 构造函数,禁止隐式类型转换
├── 原子读取操作
│   └── T get() // 原子性地读取value_的值
├── 原子修改操作
│   ├── T getAndSet(T newValue) // 原子性地将value_设置为newValue,并返回之前的值
│   ├── T getAndAdd(T x)// 原子性地将value_加上x,并返回之前的值
│   ├── T addAndGet(T x)// 原子性地将value_加上x,并返回新的值
│   ├── T incrementAndGet()	// 原子性地将value_加1,并返回之前的值
│   └── T decrementAndGet()	// 原子性地将value_减1,并返回之前的值
└── 便捷操作
    ├── void add(T x)	// 原子性地将value_加上x
    ├── void increment()	// 原子性地将value_加1
    └── void decrement()	// 原子性地将value_减1

使用typedef 定义了AtomicInt32 AtomicInt64

// 定义32位和64位原子整数类型
typedef detail::AtomicIntegerT<int32_t> AtomicInt32;
typedef detail::AtomicIntegerT<int64_t> AtomicInt64;

1.3 原子操作实现原理

  • 使用GCC内置的原子操作函数:
__sync_val_compare_and_swap  // CAS操作
__sync_lock_test_and_set     // 原子交换
__sync_fetch_and_add         // 原子加法

// 原子性地读取value_的值
T get()
{
    return __sync_val_compare_and_swap(&value_, 0, 0);
}

// 原子性地将value_设置为newValue,并返回之前的值
T getAndSet(T newValue)
{
    return __sync_lock_test_and_set(&value_, newValue);
}

// 原子性地将value_加上x,并返回之前的值
T getAndAdd(T x)
{
    return __sync_fetch_and_add(&value_, x);
}
  • 这些函数在编译时会被转换为CPU的原子指令
  • 比如x86平台会使用lock前缀的指令

1.4 在muduo中的使用场景

class Thread : noncopyable
{
   static AtomicInt32 numCreated_;  //静态变量,用来计算创建的线程序号
};


void Thread::setDefaultName()
{
  int num = numCreated_.incrementAndGet();   //创建的线程序号
  if (name_.empty())
  {
    char buf[32];
    snprintf(buf, sizeof buf, "Thread%d", num);  //设置唯一的线程名字
    name_ = buf;
  }
}
class TcpServer {
    AtomicInt32 started_;  // 标记服务是否启动
};

void TcpServer::start()
{
  if (started_.getAndSet(1) == 0)  // 默认值是0,获取之前的值并设置新值,避免程序二次启动
  {
    threadPool_->start(threadInitCallback_);

    assert(!acceptor_->listening());
    loop_->runInLoop(
        std::bind(&Acceptor::listen, get_pointer(acceptor_)));
  }
}
class Timer : noncopyable
{
    static AtomicInt64 s_numCreated_; //创建的定时器序号

    Timer(TimerCallback cb, Timestamp when, double interval)
        : callback_(std::move(cb)),
          expiration_(when),
          interval_(interval),
          repeat_(interval > 0.0),
          sequence_(s_numCreated_.incrementAndGet())  //当前定时器的序号
      { }
}

1.5 使用示例

// 测试原子操作
void testAtomic()
{
    printf("Testing Atomic operations...\n");
    
    AtomicInt32 a0;
    assert(a0.get() == 0);
    assert(a0.getAndAdd(1) == 0);
    assert(a0.get() == 1);
    assert(a0.addAndGet(2) == 3);
    assert(a0.get() == 3);
    assert(a0.incrementAndGet() == 4);
    assert(a0.get() == 4);
    assert(a0.decrementAndGet() == 3);
    assert(a0.get() == 3);
    
    printf("Atomic test passed!\n");
}

2 MutexLock/MutexLockGuard互斥量封装

2.1 封装意义

  • 提供RAII风格的互斥锁管理
  • 追踪锁的持有者,方便调试
  • 防止误用和死锁

2.2 类图

MutexLock
├── 私有成员
│   ├── pthread_mutex_t mutex_
│   └── pid_t holder_
├── 构造/析构
│   ├── MutexLock()
│   └── ~MutexLock()
├── 锁操作
│   ├── void lock()
│   └── void unlock()
├── 调试功能
│   ├── bool isLockedByThisThread()
│   └── void assertLocked()
└── 友元类
    └── Condition

MutexLockGuard
├── 私有成员
│   └── MutexLock& mutex_
├── 构造/析构
│   ├── MutexLockGuard(MutexLock& mutex)
│   └── ~MutexLockGuard()
└── 禁止拷贝
    └── noncopyable

2.3 实现原理

  • 使用pthread_mutex作为底层实现
  • 通过RAII自动管理锁的获取和释放
  • 通过gettid()追踪锁的持有者

2.4 在muduo中的使用场景

使用的场景较多,这里只展示了在EventLoop,ThreadPool中的使用。

void EventLoop::queueInLoop(Functor cb)
{
    {
        MutexLockGuard lock(mutex_);  //加锁
        pendingFunctors_.push_back(std::move(cb));
    }
    
    if (!isInLoopThread() || callingPendingFunctors_)
    {
        wakeup();
    }
}
size_t ThreadPool::queueSize() const
{
  MutexLockGuard lock(mutex_);   //加锁
  return queue_.size();
}

2.4 使用示例

muduo库封装的Mutex功能很多,但我们目前知道调用

MutexLockGuard lock(mutex);  加锁就行,其他的用法不做要求。

isLockedByThisThread 是一个用于调试和断言的重要函数,让我详细解释它的使用:

1.基本用法(掌握这里就行)

MutexLock mutex;
{
    MutexLockGuard lock(mutex);
}

2.常见使用场景(不做要求)

class ThreadSafeClass {
public:
    void method1() {
        MutexLockGuard lock(mutex_);
        // 确保在持有锁的情况下调用method2
        method2();  // 内部方法调用
    }

private:
    void method2() {
        // 确保调用此方法时已经持有锁
        mutex_.assertLocked();  // 如果未持有锁,会触发断言
        // 执行操作...
    }

    MutexLock mutex_;
};

3.调试死锁(不做要求)

class Resource {
public:
    void access() {
        MutexLockGuard lock(mutex_);
        // 检查是否真的持有锁
        if (!mutex_.isLockedByThisThread()) {
            printf("Warning: Lock not held by current thread!\n");
        }
        // 执行操作...
    }

private:
    MutexLock mutex_;
};

4.防止误用(不做要求)

class SafeCounter {
public:
    void increment() {
        // 确保在持有锁的情况下修改数据
        if (!mutex_.isLockedByThisThread()) {
            printf("Error: Must hold lock to modify counter!\n");
            return;
        }
        count_++;
    }

private:
    MutexLock mutex_;
    int count_;
};

5.实际应用示例(不做要求)

class ThreadSafeQueue {
public:
    void push(int value) {
        MutexLockGuard lock(mutex_);
        // 确保在持有锁的情况下调用内部方法
        doPush(value);
    }

private:
    void doPush(int value) {
        // 确保调用此方法时已经持有锁
        mutex_.assertLocked();
        queue_.push(value);
    }

    MutexLock mutex_;
    std::queue<int> queue_;
};

3 Condition条件变量的封装

3.1 封装意义

  • 提供线程间的同步机制
  • 实现生产者-消费者模式
  • 支持超时等待功能
  • 与MutexLock配合使用,确保线程安全

3.2 类图

Condition
├── 私有成员
│   ├── MutexLock& mutex_      // 互斥锁引用
│   └── pthread_cond_t pcond_  // 条件变量
├── 构造/析构
│   ├── explicit Condition(MutexLock& mutex)
│   └── ~Condition()
├── 等待操作
│   ├── void wait()           // 等待通知
│   └── bool waitForSeconds(double seconds)  // 超时等待
└── 通知操作
    ├── void notify()         // 通知一个等待线程
    └── void notifyAll()      // 通知所有等待线程

3.3 实现原理

本质是调用pthread线程库的接口 以及封装的MutexLock。

class Condition : noncopyable
    ....
    MutexLock& mutex_;        // 互斥锁引用
    pthread_cond_t pcond_;    // 条件变量
};

3.4 在muduo中的使用场景

用于ThreadPool任务队列的生产者-消费者模型。

用于EventLoopThread等等IO Loop线程创建成功。

class ThreadPool : noncopyable
{
    mutable MutexLock mutex_;
    Condition notEmpty_ GUARDED_BY(mutex_);
    Condition notFull_ GUARDED_BY(mutex_);  //用来处理任务队列(生产者消费者模式)
}
EventLoop* EventLoopThread::startLoop()
{
  assert(!thread_.started());
  thread_.start();

  EventLoop* loop = NULL;
  {
    MutexLockGuard lock(mutex_);
    while (loop_ == NULL)
    {
      cond_.wait();    //等等loop线程创建成功
    }
    loop = loop_;
  }

  return loop;
}


void EventLoopThread::threadFunc()
{
  EventLoop loop;

  if (callback_)
  {
    callback_(&loop);
  }

  {
    MutexLockGuard lock(mutex_);
    loop_ = &loop;
    cond_.notify();  //发通知,io loop线程已经准备好了
  }

  loop.loop();
  //assert(exiting_);
  MutexLockGuard lock(mutex_);
  loop_ = NULL;
}

使用示例

// 生产者-消费者模式
class Buffer {
public:
    Buffer(int size) : size_(size), notEmpty_(mutex_), notFull_(mutex_) {}
    
    void put(int item) {
        MutexLockGuard lock(mutex_);
        while (queue_.size() == size_) {
            notFull_.wait();  // 等待队列不满
        }
        queue_.push(item);
        notEmpty_.notify();  // 通知消费者
    }
    
    int get() {
        MutexLockGuard lock(mutex_);
        while (queue_.empty()) {
            notEmpty_.wait();  // 等待队列不空
        }
        int item = queue_.front();
        queue_.pop();
        notFull_.notify();  // 通知生产者
        return item;
    }
    
private:
    MutexLock mutex_;
    Condition notEmpty_;
    Condition notFull_;
    std::queue<int> queue_;
    int size_;
};

关键特性

  • 与MutexLock配合使用
  • 支持超时等待
  • 提供广播通知
  • 自动管理锁的释放和获取

使用建议

  • 总是与MutexLock配合使用
  • 使用while循环检查条件
  • 注意通知的时机
  • 合理使用notify和notifyAll
  • 考虑使用超时机制避免死锁

这种封装在网络库中特别有用,因为:

  • 需要处理异步事件
  • 需要实现任务队列
  • 需要处理定时器
  • 需要实现线程池
  • 需要处理生产者-消费者模式

4 完整的测试范例

完整测试代码:examples/test_atomic_mutex.cc

4.1 测试框架图

test_atomic_mutex.cc
├── 基础功能测试
│   ├── testAtomic()     // 原子操作测试
│   └── testMutex()      // 互斥锁测试
└── 条件变量测试
    └── testCondition()  // 生产者-消费者模式测试
        ├── Buffer类
        ├── producer线程
        └── consumer线程

4.2 原子操作测试流程图

testAtomic()
├── 初始化测试
│   └── AtomicInt32 a0
├── 基本操作测试
│   ├── get() == 0
│   ├── getAndAdd(1) == 0
│   └── get() == 1
├── 复合操作测试
│   ├── addAndGet(2) == 3
│   └── get() == 3
└── 自增自减测试
    ├── incrementAndGet() == 4
    ├── get() == 4
    ├── decrementAndGet() == 3
    └── get() == 3

4.3 互斥锁测试流程图

testMutex()
├── 创建互斥锁
│   └── MutexLock mutex
├── 加锁测试
│   ├── MutexLockGuard lock(mutex)
│   └── assert(mutex.isLockedByThisThread())
└── 解锁测试
    └── 作用域结束自动解锁

4.4 生产者-消费者模式原理图

[生产者线程1]     [生产者线程2]
      ↓               ↓
      └──────┐    ┌──┘
             ↓    ↓
         [Buffer缓冲区]
             ↓    ↓
      ┌──────┘    └──┐
      ↓               ↓
[消费者线程1]  [消费者线程2]  [消费者线程3]

4.5 Buffer类结构图

Buffer类
├── 私有成员
│   ├── size_t size_           // 缓冲区大小
│   ├── MutexLock mutex_       // 互斥锁
│   ├── Condition notEmpty_    // 不空条件变量
│   ├── Condition notFull_     // 不满条件变量
│   └── std::queue<int> queue_ // 数据队列
└── 公共方法
    ├── put(int item)          // 生产者方法
    └── get()                  // 消费者方法

4.6 线程同步流程图

4.6 测试输出

具体看代码打印

lesson6/build$ ./bin/test_atomic_mutex

5 章节总结

重点:

  • 原子封装的目的是提供对于项目更易用的接口,并大致了解下
  • gcc原子操作相关的函数,比如__sync_fetch_and_add
  • 了解Atomic MutexLock和Condition等封装在muduo网络库的使用。
  • 掌握MutexLock类里保存获得锁的线程ID的作用,这样更方便分析死锁问题。
#如何看待offer收割机的行为##简历中的项目经历要怎么写##项目##后端##c++#
全部评论

相关推荐

昨天 12:03
山西大学 C++
点赞 评论 收藏
分享
爸爸妈妈一直都是不善言辞的人,但他们很爱我。从大学离开家之后,每次放假回家,妈妈每顿饭都问问我吃啥,担心菜的口味咸我不爱吃,总是让我先尝第一口;到工作后,每次回到家依然保持着这样的习惯,以及增加了每次返京时,都会煮一大堆的鸡蛋和做好牛肉和鸡肉,让我带回去。在家看到我在用电脑,就不会主动打扰,以前还会让我做做家务,现在基本不会提及,都是我主动。我们对彼此的爱,都在一点一滴的小事中,行动中默默体现着。所以从小到大,我也很少用语言表达情感,更多的是行动。在长大后,读了很多书,也接触了很多生涯咨询心理咨询方面的内容,其实算在心理认知层面,对于家庭沟通上,有一些觉察和想改变的地方。从以前不记日子,爸爸妈妈的生日、父亲节母亲节,都觉得和我没有关系,到慢慢会在爸妈生日时候直接送一个红包给他们(暗搓搓的在红包说明里写爸/妈生日快乐,520等),再到新年愿望会把带爸妈体检加到关于家人的年度计划内,督促他们去做体检(虽然去年他们各种原因没去);再到这次刚好有了这样一个表白的窗口,会想要尝试着表达,直接说不太好意思,写下来就还好,刚才主动和妈妈说让她放心,我会积极找到工作,还有给妈妈买了一双她想要的鞋子改变是一点一滴发生的,慢慢来。留下一个2025母亲节的表白,妈妈,我爱你❤️
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务