【编程】C++语言编程规范-2

本文介绍了C++编程中的并发编程技巧,强调了减少线程使用、正确管理同步机制(如mutex、condition_variable)、避免数据竞争和内存泄漏的重要性,以及如何使用const和constexpr优化常量处理和内存分配

编程实践

结合C++ Effective系列参考树、尤其是工程经验教训的总结。

并发

  • 除非必要,尽量少用线程。
  • 多线程编程要守护好内存,使用atomic、mutex、condition variable、future、semaphore、latch、barrier等同步机制避免数据竞争。
  • 尽量缩小临界区,临界区指独占的资源,禁止其他线程访问变量的代码片段,如持有mutex的作用域。与回调函数相似,应尽可能精简此类操作,避免执行耗时的处理、阻塞性操作如sleep。能在临界区、回调函数外处理的,尽可能在外部。
  • 必须正确管理多线程中的对象,避免某线程正在访问的对象被另一线程清理,如用move方法使对象从一个线程正确移交给另一个线程,避免内存泄漏。
  • 使用condition variable时,必须增加条件判断并在循环中等待。下例中,线程ReceivingUnPro中条件变量错过了通知,缺少条件判断,就始终处于等待状态,而ReceivingPro依靠while判断条件,解决了问题。
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

std::condition_variable g_dataCond;
std::string g_words = "";
std::mutex g_mtx;
bool g_isReady = false;

void ReceivingUnpro() {
    std::unique_lock<std::mutex> lg(g_mtx);
    std::cout << "waiting!" << std::endl;
    g_dataCond.wait(lg);
    std::cout << "Bad receiving thread got the message: " << g_words
              << std::endl;
}
void ReceivingPro() {
    std::unique_lock<std::mutex> lg(g_mtx);
    while (!g_isReady) {
        g_dataCond.wait(lg);
    }
    std::cout << "Protected receiving thread got the message: " << g_words
              << std::endl;
}
void Sending() {
    std::lock_guard<std::mutex> lg(g_mtx);
    g_words.append("Go forward!");
    g_isReady = true;
    g_dataCond.notify_all();
}
int main() {
    std::thread b(Sending);
    b.join();
    std::thread ap(ReceivingPro);
    ap.join();
    std::thread a(ReceivingUnpro);
    a.join();
    return 0;
}

理论上,条件变量有虚假唤醒问题,所以要条件判断避免。

  • 用std::lock_gaurd、std::unique_lock确保锁被释放,不要用std::mutex的lock()、unlock()以免遗忘或异常导致dead lock。
    错误的例子,
#include <mutex>
std::mutex x;
{
    x.lock();
    // 处理数据的代码
    // 发生异常,导致后面未执行
    x.unlock(); 
}

下例中出现dead lock,

#include <unistd.h>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

using std::cout;
using std::endl;
using std::lock_guard;
using std::mutex;
using std::chrono::seconds;

mutex m1, m2, m3;
std::condition_variable cnd;
std::mutex dt_mtx;
bool ready = false;
std::vector<int> vec_i;

void ProcessData() {
    for (const auto i : vec_i) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

void T1F() {
    std::unique_lock<std::mutex> lock(dt_mtx);
    cnd.wait(lock, [] { return ready; });
    ProcessData();
}

void T2F() {
    {
        std::lock_guard<std::mutex> lock(dt_mtx);
        for (int i = 0; i < 10; i++) {
            vec_i.push_back(i);
            sleep(1);
        }
        ready = true;
    }
    cnd.notify_all();
}

void Fun1() {
    lock_guard<mutex> l1(m1);
    std::this_thread::sleep_for(seconds(1));
    lock_guard<mutex> l2(m2);
    std::this_thread::sleep_for(seconds(1));
    cout << "Fun1 is finishing" << endl;
}
void Fun2() {
    lock_guard<mutex> l1(m2);
    std::this_thread::sleep_for(seconds(1));
    lock_guard<mutex> l2(m3);
    std::this_thread::sleep_for(seconds(1));
    cout << "Fun2 is finishing" << endl;
}
void Fun3() {
    lock_guard<mutex> l1(m3);
    std::this_thread::sleep_for(seconds(1));
    lock_guard<mutex> l2(m1);
    std::this_thread::sleep_for(seconds(1));
    cout << "Fun3 is finishing" << endl;
}
int main() {
    std::thread t1(Fun1);
    std::thread t2(Fun2);
    std::thread t3(Fun3);
    if (t1.joinable()) {
        cout << "t1 is joining" << endl;
        t1.join();
    }
    if (t2.joinable()) {
        cout << "t2 is joining" << endl;
        t2.join();
    }
    if (t3.joinable()) {
        cout << "t3 is joining" << endl;
        t3.join();
    }
#ifdef VERSION1
    std::thread t1(T1F);
    std::thread t2(T2F);
    t1.join();
    t2.join();
#endif
    return 0;
}

对象与内存管理

  • 避免访问越界,如索引数组前判断下标是否超出数组区间。
  • 申请内存要先检查大小。
  • 数组作为函数参数,若是已知的固定长度,建议用std::array;若不确定长度,传递来的数组表现为指针,则函数参数要加上数组长度,或者将数组的指针与长度封装为一个类。
  • 避免函数返回其局部变量的地址,建议改为返回复制的值。
  • lambda的作用范围超出局部时,如用于线程等,按引用捕获可能导致局部变量过早被清理,应改为按值捕获;应明确捕获的变量、合理的捕获类型。
  • 使用std::make_shared、std::make_unique代替new创建的shared_ptr、unique_ptr,降低操作开销;尽量避免使用new\delete操作。
  • 默认参数new失败会抛出异常std::bad_alloc,用nothrow的new若内存分配失败,则返回nullptr
    int* p = new(std::nothrow) int[3];
    if (p==nullptr) {
        //处理错误
    }
    
    • nullptr代替NULL

常量

  • 建议优先用constexpr定义常量,编译时硬编码变量,提升效率;改关键字要求被修饰对象在编译时可确定变量的值。
  • 使用const修饰函数内部不会修改的参数,类内不变的变量、不修改不可变的成员变量的成员方法。
  • const实例化的对象,只允许调用其const方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值