1.3.2 C++新特性的线程协议

小李小李快乐不已 2025-01-10 17:24:37

目录

  • 1.3.2 C++新特性的线程协议
  • 1. C++11多线程thread
  • 1. 线程thread
  • 1. 语法
  • 2. 互斥量
  • 1. std::mutex - 独占的互斥量,不能递归使用
  • 3. std::recursive_mutex - 递归互斥量,不带超时功能
  • 5. lock_guard和unique_lock的使用和区别
  • 1. std::lock_guard - 简单的互斥量封装
  • 2. std::unique_lock - 更灵活的互斥量管理
  • 2. wait_for
  • 4. notify_one
  • 5. notify_all
  • 4. 原子变量
  • 1. 特性
  • 2. std::atomic 常见成员函数
  • 1.load
  • 2. store
  • 3. exchange
  • 4. compare_exchange_strong
  • 3. 内存顺序(Memory Order)
  • 2. std::async
  • 4. std::promise
  • 3. 常用成员函数:
  • 2. std::bind
  • 1. 特性:
  • 2. 语法:
  • 3. 可变模板参数
  • 1. 可变模板参数
  • 2. 逗号表达式展开参数包
  • 2. 异常处理的基本指导原则
  • 3. std::exception 类
  • 4. 标准异常扩展
  • 5. exception_ptr 和 std::current_exception
  • 2. ZERO_ThreadPool.cpp — 线程池实现文件
  • 3. main.cpp

1.3.2 C++新特性的线程协议

1. C++11多线程thread

1. 线程thread

1. 语法

C++11 引入了 std::thread 来实现多线程编程。std::thread 是 C++ 标准库中的线程类,用于启动和管理线程。std::thread 有几个关键的构造函数,包括默认构造函数、初始化构造函数、拷贝构造函数以及移动构造函数。

  1. 默认构造函数
    std::thread 有一个默认构造函数,它构造一个未初始化的线程对象。使用此构造函数创建的线程对象必须在之后通过赋值或者移动构造来初始化。
#include <iostream>
#include <thread>

void print_message() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    // 使用默认构造函数创建一个线程对象
    std::thread t;  // t是一个未初始化的线程对象

    // 线程创建后必须显式地启动一个线程
    t = std::thread(print_message);  // 用赋值操作来初始化线程对象
    t.join();  // 等待线程完成
    return 0;
}
//输出:Hello from thread!
  1. 初始化构造函数
    std::thread 的初始化构造函数允许通过传递线程需要执行的函数(或可调用对象)来创建线程。线程对象在创建时就会开始执行指定的任务。
#include <iostream>
#include <thread>

void print_message() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    // 使用初始化构造函数创建并启动线程
    std::thread t(print_message);  // 直接在构造时传入可调用对象
    t.join();  // 等待线程完成
    return 0;
}
//输出:Hello from thread!
  1. 拷贝构造函数
    std::thread 的拷贝构造函数是不可用的,这是因为线程是无法被复制的。线程对象在创建时会与底层操作系统的线程关联,复制线程对象会导致线程状态不一致,因此拷贝构造函数被禁用。
#include <iostream>
#include <thread>

void print_message() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t1(print_message);  // 创建线程 t1
    // std::thread t2 = t1;  // 错误:无法复制线程对象,因为拷贝构造函数被禁用

    t1.join();  // 等待线程 t1 完成
    return 0;
}
//编译错误:error: use of deleted function ‘std::thread::thread(const std::thread&)’
  1. 移动构造函数
    std::thread 提供了移动构造函数,允许将一个线程对象转移到另一个线程对象。这是必要的,因为线程对象不能被复制,但是可以通过移动语义将资源(即线程)转移到新的线程对象中。
#include <iostream>
#include <thread>

void print_message() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t1(print_message);  // 创建线程 t1

    // 使用移动构造函数,将 t1 的线程资源转移到 t2
    std::thread t2 = std::move(t1);  // t1 的资源被转移到 t2,t1 变为空线程

    // 注意:t1 现在是一个空线程,不能调用 join() 或者其他线程操作
    t2.join();  // 等待线程 t2 完成
    return 0;
}
  1. 主要成员函数
    • get_id():获取线程的唯一标识符(std::thread::id)。
    • joinable():检查线程是否可以连接(即,线程是否仍然存在且没有被连接或分离)。
    • join():等待线程执行完毕,并回收线程资源。
    • detach():将线程从当前线程管理中分离,使其独立运行,直到完成后自动回收资源。
      ```cpp
      #include
      #include
      #include

void print_thread_info(int id) {
std::cout << "Thread " << id << " is executing\n";
}

int main() {
// 创建线程对象的容器
std::vectorstd::thread threads;

// 启动5个线程
for (int i = 1; i <= 5; ++i) {
    threads.push_back(std::thread(print_thread_info, i));//线程开始执行时,会调用 print_thread_info(i),i 会作为参数传递给该函数。
    
    // 使用 get_id() 获取线程的唯一标识符
    std::cout << "Thread " << i << " ID: " << threads[i - 1].get_id() << std::endl;
    
    // 检查线程是否可加入
    if (threads[i - 1].joinable()) {
        std::cout << "Thread " << i << " is joinable\n";
    }
}

// 等待所有线程完成
for (auto& t : threads) {
    // 线程加入
    if (t.joinable()) {
        t.join();
        std::cout << "Thread with ID " << t.get_id() << " has finished.\n";
    }
}

// 创建并分离一个线程
std::thread detached_thread(print_thread_info, 100);
detached_thread.detach();  // 将线程分离,主线程不会等待它结束

std::cout << "Detached thread is now running independently.\n";

// 确保主线程在程序结束前等待所有线程完成
std::this_thread::sleep_for(std::chrono::seconds(1));  // 给分离线程一些时间运行

return 0;

}

#### 2.线程封装
`封装线程的常见方式是创建一个类来管理线程的生命周期`

ZeroThread 类将负责:
- 启动一个线程来执行某个函数。
- 提供接口来管理线程的生命周期,例如 join 和 detach。
- 防止在销毁对象时忘记处理线程的结束。
```cpp
#include <iostream>
#include <thread>
#include <functional>
#include <atomic>

class ZeroThread {
public:
    // 构造函数,接受一个可调用对象(例如函数或 lambda)
    template <typename Callable, typename... Args>
    explicit ZeroThread(Callable&& func, Args&&... args)
        : thread_(std::forward<Callable>(func), std::forward<Args>(args)...), is_joined_(false) {}

    // 禁止拷贝构造和拷贝赋值
    ZeroThread(const ZeroThread&) = delete;
    ZeroThread& operator=(const ZeroThread&) = delete;
    //在 C++11 中,= delete 是一个新特性,用于显式禁用某些函数
    /*
    ZeroThread&:表示返回当前对象的引用。
    operator=:定义赋值操作符。
    const ZeroThread& other:表示赋值操作符接受一个常量引用,其是另一个 ZeroThread 对象,将被赋值给当前对象。
    */
    // 析构函数:如果线程还没有被 join 或 detach,自动 join
    ~ZeroThread() {
        if (thread_.joinable() && !is_joined_) {
            thread_.join();  // 确保线程在销毁之前结束
        }
    }

    // join 线程
    void join() {
        if (thread_.joinable() && !is_joined_) {
            thread_.join();
            is_joined_ = true;
        }
    }

    // detach 线程
    void detach() {
        if (thread_.joinable() && !is_joined_) {
            thread_.detach();
            is_joined_ = true;
        }
    }

    // 获取线程的唯一标识符
    std::thread::id get_id() const {
        return thread_.get_id();
    }

    // 判断线程是否可以被 join
    bool joinable() const {
        return thread_.joinable();
    }

private:
    std::thread thread_;        // 用于存储线程
    std::atomic<bool> is_joined_; // 确保线程不会被 join 多次
    /*
    这是一个原子变量,用于标记线程是否已经被 join 或 detach。原子变量 (std::atomic) 确保它在多线程环境中是安全的,即使多个线程同时访问该变量,也不会引发数据竞争。

    原子操作的好处:
    std::atomic 提供了线程安全的操作,确保对 is_joined_ 的读取和写入不会发生竞争条件,从而防止多个线程在并发情况下修改该标志。
    */
};

void print_message(int thread_id) {
    std::cout << "Thread " << thread_id << " is executing" << std::endl;
}

int main() {
    // 创建一个 ZeroThread 实例,启动线程并执行 print_message 函数
    ZeroThread t1(print_message, 1);
    ZeroThread t2(print_message, 2);

    // 使用 join 等待线程完成
    t1.join();
    t2.join();

    return 0;
}

2. 互斥量

互斥量(mutex)用于在多线程环境下保护共享资源,防止数据竞争。C++ 标准库提供了多种类型的互斥量,具有不同的特性和功能。
| 类型 | 特性 | 是否支持递归 | 是否带超时功能 |
|--------------------------------|------------------------------------------------|--------------|----------------|
| std::mutex | 最基本的独占互斥量,不能递归使用 | 否 | 否 |
| std::timed_mutex | 带超时的独占互斥量,不能递归使用 | 否 | 是 |
| std::recursive_mutex | 递归互斥量,允许同一线程多次 lock | 是 | 否 |
| std::recursive_timed_mutex | 带超时的递归互斥量,允许同一线程多次 lock | 是 | 是 |

1. std::mutex - 独占的互斥量,不能递归使用

  1. 特性
    std::mutex 是最基本的互斥量类型,提供了独占锁,意味着同一时间只能有一个线程持有锁,其他线程必须等待。
    不能递归使用:如果同一个线程多次尝试获取相同的 std::mutex,会导致死锁。无法多次 lock 一个 std::mutex 对象,必须通过 unlock 来释放它。
  2. 主要成员函数
    lock():尝试获取互斥锁。如果锁未被其他线程占用,则成功获取锁;如果锁已经被占用,则阻塞当前线程,直到获取锁。
    unlock():释放互斥锁,允许其他线程获取该锁。
    try_lock():尝试获取锁。如果锁已被占用,立即返回 false;如果获取成功,返回 true,并且不阻塞线程。
    ```cpp
    #include
    #include
    #include

std::mutex mtx;

void print_numbers(int id) {
mtx.lock();
std::cout << "Thread " << id << " is printing numbers" << std::endl;
for (int i = 1; i <= 5; ++i) {
std::cout << i << " ";
}
std::cout << std::endl;
mtx.unlock();
}

int main() {
std::thread t1(print_numbers, 1);
std::thread t2(print_numbers, 2);

t1.join();
t2.join();

return 0;

}

#### 2. std::timed_mutex - 带超时的独占互斥量,不能递归使用
1. 特性
std::timed_mutex 继承自 std::mutex,与 std::mutex 相似,但它具有 带超时的特性。即,当线程请求锁时,可以指定一个最大等待时间。如果在规定时间内未能获取到锁,try_lock_for() 或 try_lock_until() 会返回 false。
不能递归使用:与 std::mutex 相同,不能多次 lock,否则会导致死锁。
2. 主要成员函数
try_lock_for(std::chrono::milliseconds):尝试获取锁,在指定的时间内等待锁。如果超时,返回 false;否则,成功获取锁并返回 truetry_lock_until(std::chrono::steady_clock::time_point):尝试在指定的时间点前获取锁,如果在指定时间点前没有获取到锁,则返回 false。
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::timed_mutex tmtx;

void try_lock_for_example(int id) {
    if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {
        std::cout << "Thread " << id << " acquired the lock!" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(50));  // Simulate work
        tmtx.unlock();
    } else {
        std::cout << "Thread " << id << " failed to acquire the lock within timeout." << std::endl;
    }
}

int main() {
    std::thread t1(try_lock_for_example, 1);
    std::thread t2(try_lock_for_example, 2);

    t1.join();
    t2.join();

    return 0;
}

3. std::recursive_mutex - 递归互斥量,不带超时功能

  1. 特性
    std::recursive_mutex 允许 递归锁定:同一个线程可以多次 lock 相同的 recursive_mutex,而不会发生死锁。当同一线程获取多次锁时,必须调用相同次数的 unlock 才能完全释放锁。
    不带超时功能:与 std::mutex 和 std::timed_mutex 不同,std::recursive_mutex 没有超时功能,只能阻塞当前线程,直到锁被释放。
  2. 主要成员函数
    lock():获取锁,如果当前线程已经拥有该锁,则可以再次调用 lock 而不会发生死锁。
    unlock():释放锁,每次调用 unlock() 必须与前面的 lock() 成对出现。
    try_lock():尝试获取锁,如果锁被占用,立即返回 false;如果获取成功,返回 true。
    ```cpp
    #include
    #include
    #include

std::recursive_mutex rmtx;

void recursive_function(int id, int count) {
if (count == 0) return;

rmtx.lock();
std::cout << "Thread " << id << " entered recursion level " << count << std::endl;
recursive_function(id, count - 1);
rmtx.unlock();

}

int main() {
std::thread t1(recursive_function, 1, 3);//1 和 3:这两个是参数,它们会被传递给 recursive_function 函数
t1.join();

return 0;

}

#### 4. std::recursive_timed_mutex - 带超时的递归互斥量
1. 特性
std::recursive_timed_mutex 是 std::recursive_mutex 和 std::timed_mutex 的结合,支持 递归锁定 和 带超时的特性。
允许同一线程递归地多次 lock,并且可以设置超时,在指定时间内无法获取锁时返回失败。
2. 主要成员函数
lock():获取锁,允许递归锁定。
unlock():释放锁。
try_lock_for(std::chrono::milliseconds):尝试获取锁,在指定的时间内等待,如果超时返回 falsetry_lock_until(std::chrono::steady_clock::time_point):尝试在指定的时间点前获取锁,如果没有获取到锁则返回 false。
```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::recursive_timed_mutex rttmtx;

void try_recursive_lock_for_example(int id, int count) {
    if (rttmtx.try_lock_for(std::chrono::milliseconds(100))) {
        std::cout << "Thread " << id << " acquired the lock!" << std::endl;
        if (count > 0) {
            try_recursive_lock_for_example(id, count - 1);  // Recursive lock
        }
        rttmtx.unlock();
    } else {
        std::cout << "Thread " << id << " failed to acquire the lock within timeout." << std::endl;
    }
}

int main() {
    std::thread t1(try_recursive_lock_for_example, 1, 3);
    t1.join();

    return 0;
}

5. lock_guard和unique_lock的使用和区别

相对于手动lock和unlock,我们可以使用RAII(通过类的构造析构)来实现更好的编码方式。
RAII:也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象

1. std::lock_guard - 简单的互斥量封装

1.特性

  • std::lock_guard 是一个简单的互斥量管理器,它在构造时自动获取锁,在析构时自动释放锁。
  • 不支持解锁:一旦锁定,不能手动释放锁,锁只会在对象销毁时释放。
  • 不能延迟锁定或解锁:一旦创建,锁定的互斥量会被 lock_guard 管理直到对象销毁。
  1. 使用场景
  • 当只需要 简单的锁定/解锁,且锁的生命周期与 lock_guard 对象的生命周期相匹配时,使用 std::lock_guard
  • 它适用于不需要复杂控制的场景,代码简洁,易于理解。
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void print_numbers(int id) {
    std::lock_guard<std::mutex> lock(mtx);  // 自动锁定和解锁
    std::cout << "Thread " << id << " is printing numbers" << std::endl;
    for (int i = 1; i <= 5; ++i) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::thread t1(print_numbers, 1);
    std::thread t2(print_numbers, 2);

    t1.join();
    t2.join();

    return 0;
}
//std::lock_guard 在 print_numbers 函数内部锁住了 mtx,并且在 print_numbers 函数结束时自动解锁。
2. std::unique_lock - 更灵活的互斥量管理
  1. 特性
    std::unique_lock 比 std::lock_guard 提供了更多的灵活性。
  • 可以手动解锁和重新锁定:它允许你在锁定期间手动释放锁(通过 unlock())或重新获取锁(通过 lock())。
  • 支持延迟锁定:你可以在 unique_lock 构造时不立即锁定互斥量,直到你调用 lock()。
  • 支持条件变量:std::unique_lock 支持与条件变量一起使用,这对于一些需要等待或通知的多线程程序非常有用。
  1. 使用场景
    如果需要更多的控制,例如手动解锁、延迟锁定或使用条件变量时,使用 std::unique_lock。
    适用于复杂的锁控制,例如需要在多个地方解锁并重新锁定的场景。
    ```cpp
    #include
    #include
    #include

std::mutex mtx;

void print_numbers(int id) {
std::unique_lockstd::mutex lock(mtx); // 手动锁定互斥量
std::cout << "Thread " << id << " is printing numbers" << std::endl;
for (int i = 1; i <= 5; ++i) {
std::cout << i << " ";
}
std::cout << std::endl;

lock.unlock();  // 手动解锁
std::cout << "Thread " << id << " released lock" << std::endl;

}

int main() {
std::thread t1(print_numbers, 1);
std::thread t2(print_numbers, 2);

t1.join();
t2.join();

return 0;

}

---


### 3. 条件变量
互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。但单靠互斥量无法实现线程的同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11对这种行为也提供了有力的支持,这就是条件变量。条件变量位于头文件condition_variable下
#### 1.条件变量使用过程:
1. 拥有条件变量的线程获取互斥量;
2. 循环检查某个条件,如果条件不满足则阻塞直到条件满足;如果条件满足则向下执行;
3. 某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有等待线程。

#### 2.成员函数


| 函数                | 说明                                                             | 适用场景                                                |
|---------------------|------------------------------------------------------------------|---------------------------------------------------------|
| `wait`              | 阻塞当前线程直到条件变量满足或被唤醒。                            | 当线程需要等待某个条件(通常需要与循环一起使用)。      |
| `wait_for`          | 阻塞当前线程直到条件满足或超时。                                  | 当需要限制等待时间时,适用于等待超时的场景。             |
| `wait_until`        | 阻塞当前线程直到条件满足或达到指定时间点。                        | 适用于等待直到某个特定的时间点。                        |
| `notify_one`        | 唤醒一个等待条件变量的线程。                                      | 当某个条件满足时,只唤醒一个等待线程。                   |
| `notify_all`        | 唤醒所有等待条件变量的线程。                                      | 当某个条件满足且所有等待线程都应继续执行时。             |

##### 1. wait
wait 是最常用的条件变量成员函数,用于使线程等待一个条件成立。它会释放与条件变量关联的互斥量,然后挂起线程的执行,直到其他线程通知它。
1. 语法
```cpp
void wait(std::unique_lock<std::mutex>& lock);

lock:一个已经锁定的 std::unique_lock 对象,表示条件变量等待时释放的互斥量。
当 wait 被调用时,条件变量会释放 lock,并将调用线程挂起,直到其他线程通过 notify_one 或 notify_all 唤醒它。
2. 注意事项:
由于虚假唤醒(spurious wakeups)问题,通常需要将 wait 放在循环中,直到实际的条件成立。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; }); // 等待条件成立
    std::cout << "Thread " << id << " is running" << std::endl;
}

void go() {
    std::unique_lock<std::mutex> lock(mtx);
    ready = true;
    cv.notify_all(); // 唤醒所有等待的线程
}

int main() {
    std::thread threads[10];
    
    for (int i = 0; i < 10; ++i) {
        threads[i] = std::thread(print_id, i);
    }

    std::cout << "Waiting..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));

    go();  // 唤醒所有线程

    for (auto& t : threads) {
        t.join();
    }

    return 0;
}
2. wait_for

wait_for 函数用于等待条件满足或者等待指定的超时时间。如果在超时时间内条件不满足,函数会返回,并且条件不会被改变。

  1. 语法:
    template <class Rep, class Period>
    std::cv_status wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time);
    
    lock:一个已经锁定的 std::unique_lock 对象,表示条件变量等待时释放的互斥量。
    rel_time:表示等待的时间,通常使用 std::chrono 类型。
    返回值:
    std::cv_status:表示条件是否满足。可以是 std::cv_status::no_timeout 或 std::cv_status::timeout。
  2. 注意事项:
    如果在指定时间内条件满足,线程将继续执行。如果超时,则返回 timeout。
    ```cpp
    #include
    #include
    #include
    #include
    #include

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
std::unique_lockstd::mutex lock(mtx);
if (cv.wait_for(lock, std::chrono::seconds(2), []{ return ready; })) {
std::cout << "Thread " << id << " is running" << std::endl;
} else {
std::cout << "Thread " << id << " timed out" << std::endl;
}
}

void go() {
std::unique_lockstd::mutex lock(mtx);
ready = true;
cv.notify_all(); // 唤醒所有线程
}

int main() {
std::thread threads[10];

for (int i = 0; i < 10; ++i) {
    threads[i] = std::thread(print_id, i);
}

std::cout << "Waiting..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));

go();  // 唤醒所有线程

for (auto& t : threads) {
    t.join();
}

return 0;

}

##### 3. wait_until
wait_until 函数类似于 wait_for,但是它等待`一个特定的时间点`,而不是持续等待一段时间。

1. 语法
```cpp
template <class Clock, class Duration>
std::cv_status wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& timeout_time);

lock:一个已经锁定的 std::unique_lock 对象。
timeout_time:指定的绝对时间点,通常使用 std::chrono::time_point 类型。
返回值:
std::cv_status:表示条件是否满足。可以是 std::cv_status::no_timeout 或 std::cv_status::timeout。
2. 注意事项:
适用于需要等待直到某个具体时间点的场景。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(2);
    if (cv.wait_until(lock, timeout, []{ return ready; })) {
        std::cout << "Thread " << id << " is running" << std::endl;
    } else {
        std::cout << "Thread " << id << " timed out" << std::endl;
    }
}

void go() {
    std::unique_lock<std::mutex> lock(mtx);
    ready = true;
    cv.notify_all(); // 唤醒所有线程
}

int main() {
    std::thread threads[10];
    
    for (int i = 0; i < 10; ++i) {
        threads[i] = std::thread(print_id, i);
    }

    std::cout << "Waiting..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));

    go();  // 唤醒所有线程

    for (auto& t : threads) {
        t.join();
    }

    return 0;
}
4. notify_one

notify_one 通知等待条件变量的 一个线程,使其从 wait、wait_for 或 wait_until 中返回。

  1. 语法:

    void notify_one();
    
  2. 注意事项:
    notify_one 唤醒一个等待中的线程。如果没有线程在等待,它不会有任何效果。

    cv.notify_one();  // 唤醒一个等待的线程
    
    5. notify_all

    notify_all 通知所有等待条件变量的线程,使它们都从 wait、wait_for 或 wait_until 中返回。

  3. 语法:

    void notify_all();
    
  4. 注意事项:
    notify_all 唤醒所有等待的线程。通常用于当某个条件已变更且所有等待的线程都应当继续执行时。

    cv.notify_all();  // 唤醒所有等待的线程
    

4. 原子变量

  • 原子变量(std::atomic)是一种提供原子操作的类型,用于在线程间共享数据时避免数据竞争。原子操作是指在执行时不会被中断或分割的操作,保证数据的完整性。在并发程序设计中,原子变量通常用于实现高效的锁-free 编程,避免了传统锁(如 std::mutex)的性能开销。
  • std::atomic 是 C++11 引入的模板类,位于 头文件中。原子变量支持多个线程并发地执行操作,同时保证对该变量的操作不会被中断或干扰。

1. 特性

  1. 原子性:对原子变量的操作(如读、写、增减)是不可中断的,确保线程安全。
  2. 无锁:相比于传统的锁(如 std::mutex),原子操作不需要加锁和解锁,减少了上下文切换的开销。
    适用于简单类型:例如 int、bool、float、pointer 等。

    2. std::atomic 常见成员函数

    1.load
    获取原子变量的值。可以指定内存顺序(如 memory_order_relaxed,memory_order_acquire 等)。
    T load(memory_order order = memory_order_seq_cst) const;
    
    2. store
    设置原子变量的值。可以指定内存顺序。
    void store(T desired, memory_order order = memory_order_seq_cst);
    
    3. exchange
    将原子变量的当前值与提供的值交换,并返回原来的值。
    T exchange(T desired, memory_order order = memory_order_seq_cst);
    
4. compare_exchange_strong

如果原子变量的当前值与期望值相等,则将其设置为新值。此操作原子地执行。

bool compare_exchange_strong(T& expected, T desired, memory_order success_order = memory_order_seq_cst, memory_order failure_order = memory_order_seq_cst);

3. 内存顺序(Memory Order)

原子操作通常会对内存操作的顺序产生影响。C++ 提供了不同的内存顺序控制,以便优化多线程程序的执行。

  1. memory_order_relaxed:没有任何同步要求,操作可以在其他操作之前或之后发生。
  2. memory_order_consume:仅影响依赖于该操作的内存访问顺序,通常与memory_order_acquire 等价。
  3. memory_order_acquire:在此操作之前的所有操作必须先于此操作执行。
  4. memory_order_release:此操作之后的所有操作必须在此操作之后执行。
  5. memory_order_acq_rel:结合了 memory_order_acquire 和 memory_order_release,要求操作前后的内存访问顺序。
  6. memory_order_seq_cst(默认):所有操作的顺序都是严格的。
    ```cpp
    #include
    #include
    #include

std::atomic counter(0); // 定义一个原子变量

void increment() {
for (int i = 0; i < 1000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed); // 使用原子加法操作
}
}

int main() {
std::thread t1(increment);
std::thread t2(increment);

t1.join();
t2.join();

std::cout << "Counter: " << counter.load(std::memory_order_relaxed) << std::endl; // 输出原子变量的值
return 0;

}

---

### 5. 异步操作
异步操作可以通过 std::future、std::async、std::packaged_task 和 std::promise 来实现。这些工具允许我们在`不同的线程间`进行`数据传递、任务分发及结果获取`,而不需要显式的线程管理。


| 类/函数              | 说明                                                               | 使用场景                         |
|----------------------|--------------------------------------------------------------------|----------------------------------|
| `std::future`         | 用于获取异步任务的结果,提供 `get()` 和 `wait()` 等成员函数。     | 异步任务的结果获取。              |
| `std::async`          | 启动异步任务并返回一个 `std::future`。可以选择任务在新线程执行。  | 异步任务的启动,适合简单的任务执行。|
| `std::packaged_task`  | 将可调用对象包装成任务,并通过 `get_future()` 返回 `std::future`。 | 需要在特定线程中执行的异步任务。 |
| `std::promise`        | 设置异步任务的结果,通过 `get_future()` 返回 `std::future`。      | 异步任务的结果设置,提供线程间的通信。|

#### 1. std::future
std::future 用于`获取异步操作的结果`。它可以与 std::async、std::promise 或 std::packaged_task 配合使用来获取异步操作的返回值。

1. 成员函数:
- get():阻塞调用,直到异步操作完成并返回结果。
- valid():检查 std::future 是否有效,表示是否可以获取结果。
- wait():阻塞当前线程,直到异步任务完成。
```cpp
#include <iostream>
#include <future>
#include <chrono>

int do_work() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

int main() {
    std::future<int> result = std::async(std::launch::async, do_work);
    
    std::cout << "Doing other work..." << std::endl;

    // 等待异步任务结果
    int value = result.get();  // 阻塞直到 do_work 完成
    std::cout << "Result: " << value << std::endl;

    return 0;
}

//std::async 启动一个异步任务,返回一个 std::future 对象。
//使用 result.get() 获取异步任务的返回值,会阻塞当前线程,直到 do_work 完成。

2. std::async

std::async 用于启动一个异步任务,返回一个 std::future,通过它可以获取异步任务的结果。

  1. 语法:
    std::future<T> std::async(std::launch policy, Callable&& f, Args&&... args);
    
  • policy:指定异步任务的启动策略,std::launch::async 表示任务会在新线程中执行,
  • std::launch::deferred 表示任务会在调用 get() 时执行。
  • Callable:一个可调用的对象(如函数指针、lambda 表达式等)。
  • Args:传递给 Callable 的参数。
    ```cpp
    #include
    #include

int add(int a, int b) {
return a + b;
}

int main() {
std::future result = std::async(std::launch::async, add, 3, 4);

std::cout << "Waiting for the result..." << std::endl;
std::cout << "Result: " << result.get() << std::endl;

return 0;

}
//使用 std::async 启动一个异步任务,该任务计算 add(3, 4),并返回结果。
//调用 result.get() 时,主线程会阻塞,直到异步任务完成。

#### 3. std::packaged_task
std::packaged_task 将一个可调用对象(如函数、lambda 表达式等)包装成一个任务,并返回一个 std::future 结果对象。你可以将 std::packaged_task 与 std::thread 一起使用。

1. 语法:
```cpp
std::packaged_task<T()> task(f);
std::future<T> get_future();

task():传入一个可调用对象 f,将其包装成任务。
get_future():获取与任务关联的 std::future 对象。

#include <iostream>
#include <future>
#include <thread>

int multiply(int x, int y) {
    return x * y;
}

int main() {
    std::packaged_task<int(int, int)> task(multiply);  // 包装任务
    std::future<int> result = task.get_future();      // 获取 future
    
    // 在新线程中执行任务
    std::thread t(std::move(task), 4, 5);
    t.join();  // 等待线程完成

    std::cout << "Result: " << result.get() << std::endl;  // 获取异步任务的结果

    return 0;
}

4. std::promise

std::promise 用于设置异步操作的结果,并与 std::future 配合使用。std::promise 会在某个线程中设置结果,而 std::future 则在另一个线程中获取结果。

  1. 语法:
    std::promise<T> promise;
    std::future<T> promise.get_future();
    
    get_future():返回与 promise 关联的 std::future 对象,用于获取结果。
    ```cpp
    #include
    #include
    #include

void set_value(std::promise& prom) {
std::this_thread::sleep_for(std::chrono::seconds(2));
prom.set_value(10); // 设置结果
}

int main() {
std::promise prom;
std::future result = prom.get_future(); // 获取 future

std::thread t(set_value, std::ref(prom));  // 在新线程中设置结果
t.join();

std::cout << "Result: " << result.get() << std::endl;  // 获取结果

return 0;

}
/*
std::promise 在 set_value 函数中设置结果。
std::future 用于从主线程获取结果。
std::promise 和 std::future 之间建立了一个数据传递通道。
*/



## 2. function和bind

### 1. std::function
std::function 是 C++11 引入的模板类,它可以包装任何可以调用的目标,包括普通函数、Lambda 表达式、函数指针、成员函数指针、以及其他的可调用对象。它提供了一种统一的方式来存储和调用函数。

#### 1.特性:
1. 可存储任何可调用对象:可以存储普通函数、函数指针、Lambda 表达式、成员函数指针等。
2. 类型安全:通过模板保证了类型安全。
3. 多态性:能够支持不同类型的可调用对象,使得函数可以作为参数或返回值传递。
#### 2. 语法:
```cpp
std::function<ReturnType(Args...)> function_name;

3. 常用成员函数:

operator():调用存储的可调用对象。
get():获取存储的可调用对象。

#include <iostream>
#include <functional>

void hello() {
    std::cout << "Hello, world!" << std::endl;
}

int add(int a, int b) {
    return a + b;
}

int main() {
    // 存储普通函数
    std::function<void()> func1 = hello;
    func1();  // 调用函数

    // 存储带参数的函数
    std::function<int(int, int)> func2 = add;
    std::cout << "Sum: " << func2(3, 4) << std::endl;

    // 存储 Lambda 表达式
    std::function<int(int, int)> func3 = [](int a, int b) { return a * b; };
    std::cout << "Product: " << func3(3, 4) << std::endl;

    return 0;
}

2. std::bind

std::bind 用于绑定函数函数对象的参数,使得函数的某些参数预先固定,从而创建一个新的可调用对象。它通常与 std::function 一起使用,使得函数更灵活。

1. 特性:

预先绑定参数:允许部分指定参数值,返回一个新的可调用对象。
创建适配器:可以将不匹配的函数签名转换为所需的签名。
函数适配:通过 std::bind,可以适配成员函数、全局函数等。

2. 语法:

auto bound_function = std::bind(Function, arg1, arg2, ..., argN);

Function:需要绑定的可调用对象(函数、成员函数、Lambda 等)。
arg1, arg2, ..., argN:部分或全部参数,可以使用 std::placeholders 来指定占位符。
常用占位符:
std::placeholders::_1, _2, ..., _N:表示绑定时未指定的参数。

#include <iostream>
#include <functional>

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

int main() {
    // 使用 std::bind 绑定部分参数
    auto add_5 = std::bind(add, 5, std::placeholders::_1);  // 绑定第一个参数为 5
    std::cout << "5 + 10 = " << add_5(10) << std::endl;  // 调用时只需要提供第二个参数

    // 使用 std::bind 绑定全部参数
    auto multiply_2 = std::bind(multiply, std::placeholders::_1, 2);  // 绑定第二个参数为 2
    std::cout << "6 * 2 = " << multiply_2(6) << std::endl;

    return 0;
}

3. 可变模板参数

在 C++11 中,引入了可变模板参数(Variadic Templates),这使得模板能够接受不定数量的模板参数。对于这些可变参数,可以通过递归展开和逗号表达式展开来进行处理。

1. 可变模板参数

可变模板参数允许我们在模板定义时不指定参数的数量,参数的个数可以在实例化时指定。这为 C++ 提供了强大的泛型编程能力。

  1. 基本语法:
    template <typename... Args>  // Args 为一个参数包
    void func(Args... args) {
     // 函数体
    }
    
    Args... 是一个参数包,代表了一组类型参数。
    在函数体内,你可以像访问其他类型一样使用参数包 args。
  2. 举例
    ```cpp
    #include

template <typename... Args>
void print(Args... args) {
// 通过折叠表达式展开参数包
(std::cout << ... << args) << std::endl;
}

int main() {
print(1, 2.5, "Hello", 'a'); // 输出: 12.5Helloa
print("C++", "is", "awesome"); // 输出: C++isawesome
return 0;
}

### 2. 展开参数包
#### 1.  递归方式展开参数包
递归展开参数包的方式是通过递归模板函数来逐个处理每个参数,直到参数包为空为止。这是一种比较经典的展开方式。

```cpp
#include <iostream>

// 基本情况,参数包为空时停止递归
void print() {
    std::cout << "End of recursion" << std::endl;
}

// 递归展开参数包
template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";
    print(rest...);  // 递归处理剩余的参数
}

int main() {
    print(1, 2.5, "Hello", 'a');
    return 0;
}
//输出:1 2.5 Hello a End of recursion
/*
print() 是基本递归函数,它作为递归的终止条件。
print(T first, Args... rest) 展开了参数包,打印当前的第一个参数 first,并递归地处理剩下的参数 rest...。
递归逐渐缩小参数包,直到参数包为空,递归终止。
*/

2. 逗号表达式展开参数包

逗号表达式(Comma Expressions)可以用于在一个表达式中同时处理多个参数。逗号表达式会从左到右依次计算每个参数,并返回最后一个参数的值。在模板中,我们可以利用这一特性来展开参数包。

#include <iostream>

// 使用逗号表达式展开参数包
template <typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << std::endl;  // 逗号表达式展开
}

int main() {
    print(1, 2.5, "Hello", 'a');
    return 0;
}
/*
输出:12.5Helloa
通过 (std::cout << ... << args) 使用了 C++17 引入的折叠表达式(fold expression)。
折叠表达式是一种简化的语法,可以直接在多个参数上执行相同的操作,简洁地展开参数包。
*/
  • 折叠表达式
    ```cpp
    #include

template <typename... Args>
void print(Args... args) {
std::cout << (args + ...); // 对所有参数进行加法折叠
}

int main() {
print(1, 2, 3, 4); // 输出 10
return 0;
}


## 4. 异常处理
### 1. 异常处理的基本语法
C++ 的异常处理机制基于 try-catch 块。通过 throw 抛出异常,通过 catch 捕获异常。
```cpp
try {
    // 代码块中可能抛出异常
    if (/* some condition */) {
        throw SomeException();  // 抛出异常
    }
} catch (const SomeException& e) {
    // 异常处理代码
    std::cout << "Exception caught: " << e.what() << std::endl;
} catch (const std::exception& e) {
    // 处理其他标准异常
    std::cout << "Standard exception: " << e.what() << std::endl;
}

2. 异常处理的基本指导原则

  1. 尽早捕获异常:捕获异常的顺序应尽量从具体类型到基类类型,这样可以确保最合适的异常被捕获。
  2. 只处理能处理的异常:异常应该在能够合理处理的地方捕获。如果无法处理异常,可以重新抛出(throw)或记录日志并终止程序。
  3. 避免捕获通用异常:不要随意捕获 std::exception,应该尽可能明确异常类型,因为这样有助于理解错误的具体原因。
  4. 避免在析构函数中抛出异常:析构函数中抛出的异常无法被捕获,可能导致程序终止。应该避免在析构函数中抛出异常,或者保证异常不会传播。
  5. 确保资源释放:使用 RAII(资源获取即初始化)模式来确保资源在异常发生时能正确释放。例如,std::unique_ptr 和 std::shared_ptr 能自动释放内存。

3. std::exception 类

std::exception 是所有标准异常类的基类,它提供了一个 what() 函数来返回异常的描述信息。大部分标准异常类都继承自 std::exception。

#include <iostream>
#include <exception>

class MyException : public std::exception {
public:
    const char* what() const noexcept override {
        return "My custom exception!";
    }
};

int main() {
    try {
        throw MyException();
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

4. 标准异常扩展

C++ 标准库中有多个异常类,它们都继承自 std::exception,每个异常类表示不同类型的错误。

异常类 说明
std::exception 所有标准异常的基类,提供 what() 方法来获取异常信息
std::bad_alloc 表示内存分配失败(通常由 new 抛出)
std::bad_cast 用于类型转换失败,通常在 dynamic_cast 中抛出
std::bad_typeid 用于类型信息查询失败(如不适用于 typeid)
std::out_of_range 当访问容器或数组的越界元素时抛出
std::invalid_argument 表示传给函数的参数无效
std::overflow_error 数值溢出错误
std::underflow_error 数值下溢错误
std::logic_error 逻辑错误,通常是程序的设计或实现错误
std::runtime_error 运行时错误,表示程序在运行时遇到的问题

#include <iostream>
#include <stdexcept>

void test_out_of_range() {
    throw std::out_of_range("Out of range error!");
}

int main() {
    try {
        test_out_of_range();
    } catch (const std::out_of_range& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

5. exception_ptr 和 std::current_exception

C++11 引入了 std::exception_ptr,它允许我们捕获当前抛出的异常,并在后续的代码中重新抛出该异常。这对于在异常处理中转发异常或者跨线程传递异常特别有用。

  • std::current_exception() 用于获取当前正在抛出的异常。
  • std::rethrow_exception() 用于重新抛出一个 exception_ptr。
    ```cpp
    #include
    #include

void func() {
try {
throw std::runtime_error("An error occurred in func");
} catch (...) {
// 捕获所有异常,并保存异常指针
std::exception_ptr ptr = std::current_exception();
throw ptr; // 将异常指针重新抛出
}
}

int main() {
try {
func();
} catch (const std::exception_ptr& e) {
try {
std::rethrow_exception(e); // 重新抛出异常
} catch (const std::exception& ex) {
std::cout << "Caught exception: " << ex.what() << std::endl;
}
}
return 0;
}
//在 func() 中,我们首先抛出一个异常,然后通过 std::current_exception() 获取当前抛出的异常。
//在 main() 中,通过 std::rethrow_exception() 重新抛出这个异常,并捕获它进行处理。


## 5.实现线程池
### 1.  ZERO_ThreadPool.h — 线程池头文件
```cpp
#ifndef ZERO_THREADPOOL_H
#define ZERO_THREADPOOL_H

#include <iostream>
#include <vector>
#include <thread>
#include <functional>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <chrono>

class ZERO_ThreadPool {
public:
    // 构造函数
    ZERO_ThreadPool();

    // 初始化线程池,设置线程数
    void init(size_t numThreads);

    // 启动线程池
    void start();

    // 提交任务到线程池
    template <typename Func, typename... Args>
    void exec(Func&& func, Args&&... args);

    // 等待所有任务完成,最多等待 `timeoutMs` 毫秒
    bool waitForAllDone(int timeoutMs = -1);

    // 停止线程池
    void stop();

    // 析构函数
    ~ZERO_ThreadPool();

private:
    // 工作线程函数
    void workerThread();

    // 任务队列
    std::queue<std::function<void()>> taskQueue;

    // 线程池状态变量
    std::vector<std::thread> workers;
    std::mutex queueMutex;
    std::mutex doneMutex;
    std::condition_variable condition;
    std::condition_variable doneCondition;
    std::atomic<bool> stopFlag;
    std::atomic<size_t> activeThreads;
    size_t threadCount;
};

#endif // ZERO_THREADPOOL_H

2. ZERO_ThreadPool.cpp — 线程池实现文件

#include "ZERO_ThreadPool.h"

// 构造函数
ZERO_ThreadPool::ZERO_ThreadPool() : stopFlag(false), threadCount(0), activeThreads(0) {}

// 初始化线程池,设置线程数
void ZERO_ThreadPool::init(size_t numThreads) {
    if (numThreads == 0) {
        std::cerr << "Thread pool must have at least one thread." << std::endl;
        return;
    }
    threadCount = numThreads;
}

// 启动线程池
void ZERO_ThreadPool::start() {
    assert(threadCount > 0 && "Thread pool not initialized.");
    /*
    assert 是一种断言宏,它在调试模式下会检查条件是否成立。如果条件为 false,则会打印错误信息并终止程序。
    这里检查 threadCount > 0,确保线程池在启动前已正确初始化且线程数大于零。如果 threadCount 小于等于零,将导致程序终止并显示错误信息 "Thread pool not initialized."
    */
    for (size_t i = 0; i < threadCount; ++i) {
        workers.push_back(std::thread(&ZERO_ThreadPool::workerThread, this));
        //               创建一个新线程  函数指针  默认每个线程的执行函数 指向当前对象指针
    }
}

// 提交任务到线程池
template <typename Func, typename... Args>
void ZERO_ThreadPool::exec(Func&& func, Args&&... args) {
    {
        std::lock_guard<std::mutex> lock(queueMutex);
        taskQueue.push(std::bind(std::forward<Func>(func), std::forward<Args>(args)...));//完美接发
    }
    condition.notify_one();
}

// 等待所有任务完成,最多等待 `timeoutMs` 毫秒
bool ZERO_ThreadPool::waitForAllDone(int timeoutMs) {
    std::unique_lock<std::mutex> lock(doneMutex);
    if (timeoutMs < 0) {
        doneCondition.wait(lock, [this]() { return taskQueue.empty() && activeThreads == 0; });
        //会让当前线程阻塞,直到条件 taskQueue.empty() && activeThreads == 0 为真
    } else {
        auto timeout = std::chrono::milliseconds(timeoutMs);
        doneCondition.wait_for(lock, timeout, [this]() { return taskQueue.empty() && activeThreads == 0; });
        /*
        wait_for 是条件变量提供的一个阻塞函数,它会使当前线程阻塞指定的时间(在这里是 timeout),直到以下两个条件之一成立:
        1. 条件满足,线程可以继续执行。
        2. 超过了指定的超时时间。
        第一个参数:lock 是一个已经锁定的 std::unique_lock,它是条件变量的同步基础。
        第二个参数:timeout 是一个 std::chrono::milliseconds 类型的超时值,表示线程阻塞的最大时间。
        第三个参数:pred 是一个返回布尔值的条件函数。当这个函数返回 true 时,wait_for 会返回并且释放锁。
        */
    }
    return taskQueue.empty() && activeThreads == 0;
}

// 停止线程池
void ZERO_ThreadPool::stop() {
    {
        std::lock_guard<std::mutex> lock(queueMutex);
        stopFlag = true;
    }
    condition.notify_all();//唤醒所有等待 condition 条件变量的线程
    for (auto& worker : workers) {
        if (worker.joinable()) {
            worker.join();//join() 是一个阻塞操作,它会阻塞当前线程直到该线程完成执行。调用 join() 可以确保在程序退出前,所有的线程都已经正常完成。
        }
    }
}

// 析构函数
ZERO_ThreadPool::~ZERO_ThreadPool() {
    stop();
}

// 工作线程函数
void ZERO_ThreadPool::workerThread() {
    while (true) {
        std::function<void()> task;
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            condition.wait(lock, [this]() { return !taskQueue.empty() || stopFlag; });

            if (stopFlag && taskQueue.empty()) {
                return;
            }

            task = std::move(taskQueue.front());
            //将队列中队首任务所有权转移到task
            taskQueue.pop();
        }

        ++activeThreads;
        task();
        --activeThreads;

        doneCondition.notify_one();
        //条件变量,用来同步主线程或其他线程。通知一个线程说明当前线程完成了任务,主线程可以在适当的时候执行相应的操作(如销毁线程、处理结果等)
    }
}

// 显示模板函数的定义
template void ZERO_ThreadPool::exec<void(*)(int), int>(void(*)(int), int);
//显式实例化模板函数可以避免编译器在每次调用模板时都进行模板实例化,从而提高编译速度

3. main.cpp

#include <iostream>
#include "ZERO_ThreadPool.h"

void testFunction(int taskId) {
    std::cout << "Task " << taskId << " is being processed by thread " << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(500));  // 模拟耗时任务
}

int main() {
    // 创建线程池
    ZERO_ThreadPool tpool;

    // 初始化线程池,设置线程数为5
    tpool.init(5);

    // 启动线程池
    tpool.start();

    // 将任务丢到线程池中,提交5个任务
    for (int i = 0; i < 5; ++i) {
        tpool.exec(testFunction, i);
    }

    // 等待所有任务完成,最多等待1000毫秒
    if (tpool.waitForAllDone(1000)) {
        std::cout << "All tasks are done." << std::endl;
    } else {
        std::cout << "Timeout waiting for tasks to complete." << std::endl;
    }

    // 停止线程池
    tpool.stop();

    return 0;
}

https://github.com/0voice

...全文
33 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。 目 录 第1部分C++线程系统编程 第1章线程安全的对象生命期管理3 1.1当析构函数遇到多线程. . . . . . . . . . . . . . . . .. . . . . . . . . . . 3 1.1.1线程安全的定义. . . . . . . . . . . . . . . . .. . . . . . . . . . . 4 1.1.2MutexLock 与MutexLockGuard. . . . . . . . . . . . . . . . . . . . 4 1.1.3一个线程安全的Counter 示例.. . . . . . . . . . . . . . . . . . . 4 1.2对象的创建很简单. . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 5 1.3销毁太难. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . 7 1.3.1mutex 不是办法. . . . . . . . . . . . . . . . . . . .. . . . . . . . 7 1.3.2作为数据成员的mutex 不能保护析构.. . . . . . . . . . . . . . 8 1.4线程安全的Observer 有多难.. . . . . . . . . . . . . . . . . . . . . . . . 8 1.5原始指针有何不妥. . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 11 1.6神器shared_ptr/weak_ptr . . . . . . . . . .. . . . . . . . . . . . . . . . 13 1.7插曲:系统地避免各种指针错误. . . . . . . . . . . . . . . . .. . . . . . 14 1.8应用到Observer 上.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.9再论shared_ptr 的线程安全.. . . . . . . . . . . . . . . . . . . . . . . . 17 1.10shared_ptr 技术与陷阱. . . .. . . . . . . . . . . . . . . . . . . . . . . . 19 1.11对象池. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 21 1.11.1enable_shared_from_this . . . . . . . . . . . . . . . . . . . . . . 23 1.11.2弱回调. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . 24 1.12替代方案. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . 26 1.13心得与小结. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . 26 1.14Observer 之谬. . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 第2章线程同步精要 2.1互斥器(mutex). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.1.1只使用非递归的mutex . . . . . . . . . . . . . .. . . . . . . . . . 33 2.1.2死锁. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 35 2.2条件变量(condition variable). . . . . . . . . .

400

社区成员

发帖
与我相关
我的任务
社区描述
零声学院,目前拥有上千名C/C++开发者,我们致力将我们的学员组织起来,打造一个开发者学习交流技术的社区圈子。
nginx中间件后端 企业社区
社区管理员
  • Linux技术狂
  • Yttsam
  • 零声教育-晚晚
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

请新加入的VIP学员,先将自己参加活动的【所有文章】,同步至社区:

【内容管理】-【同步至社区-【零声开发者社区】

试试用AI创作助手写篇文章吧