C++并发编程实战——第二章 线程管理

第二章 线程管理

2.1 线程的基本操作

2.1.1 启动线程
  • 每个程序至少有一个执行main()函数的线程,其他线程与主线程同时运行。

  • 和主线程执行完main()会退出一样,其他线程执行完也会退出。

    • 简单的启动线程,就是构造thread对象

    • 除了用函数进行构造,还可以用函数对象,也就是仿函数进行构造

#include <iostream>
#include <thread>

using namespace std;

void fun() {
    cout << "fun" << endl;
}

class fun_2 {
public:
    void operator()() const {
        cout << "fun_2" << endl;
    }
};

int main() {
    //用函数来构造
    thread my_thread_01(fun);

    //也可以用函数对象来构造
    fun_2 my_fun;
    thread my_thread_02(my_fun);
}
  • 但是用函数对象构造需要注意,不要加(),加上之后会变成“声明了一个名为my_thread的函数,这个函数带有一个参数(函数指针指向没有参数并返回 background_task对象的函数),返回一个 std::thread 对象的函数。
thread my_thread_02(my_fun); //正确
thread my_thread_02(my_fun()); //错误
  • 还可以使用lambda表达式
thread my_thread_03([]() {
    cout << "lambda" << endl;
});
  • 线程启动后,可以选择是等待线程执行结束,或者让其自主运行,也就是汇入join分离detach
    • 让其自主运行的时候,需要小心访问数据的有效性
    • 通常传递给线程的是值,让其复制到自己的线程中去,但是如果传递的是引用和指针,就需要谨慎考虑数据是否已经在主程序结束后被销毁。
#include <iostream>
#include <thread>

using namespace std;

struct func {
    int& i;
    func(int& i_) : i(i_) {}
    void operator() () {
        for (unsigned j = 0; j < 100; ++j) {
            i++; // 1 潜在访问隐患:空引用
        }
    }
};
int main() {
    int some_local_state = 0;
    func my_func(some_local_state);
    std::thread my_thread(my_func);
    my_thread.detach(); // 2 不等待线程结束
} // 3 新线程可能还在运行
2.1.2 等待线程完成
  • 使用join()等待线程完成
  • 当你需要对等待中的线程有更灵活的控制时,比如:看一下某个线程是否结束,或者只等待一段时间(超过时 间就判定为超时)。想要做到这些,需要使用其他机制来完成,比如条件变量和future
  • 调用join(),还可以清 理了线程相关的内存,这样 std::thread 对象将不再与已经完成的线程有任何关联
  • 只能对一个线 程使用一次join()
2.1.3 特殊情况下的等待
  • 如果想要分离线程,可以在线程启动 后,直接使用detach()进行分离。
  • 如果等待线程,则需要细心挑选使用join()的位置。

2.2 传递参数

  • 为带参数的函数创建进程,函数的默认实参会被忽略
#include <thread>

void f(int i = 1) {}

int main() {
    std::thread t(f);// 报错,因为f需要一个参数,并且默认的值会被忽略
    std::thread t(f,42);//正确
}
  • 参数的引用类型也会被忽略,因此需要使用std::ref()
#include <iostream> 
#include <thread>

void f(int& i) {
    i++;
}

int main() {
    int i = 0;
    //报错,因为thread会忽略引用类型,会把i拷贝一个临时对象,作为右值传递给f(),但是f()是接受非常量引用,即左值的,编译器报错
    //std::thread t(f, i);
    std::thread t(f, std::ref(i));
    t.join();

    std::cout << i << std::endl;
}
  • 如果对一个实例的 non-static 成员函数创建线程,第一个参数类型为成员函数指针,第二个参数类型为实例指针,后续参数为函数参数
    • 第一种传地址,则不会有构造函数的调用
    • 第二种传对象,则会先以对象为参数进行拷贝构造,得到一个临时对象传入thread在进行构造一个新的A对象,也就是会调用两次构造函数,但是这里在VS中会优化掉右值构造的那次隐式构造。
#include <iostream>
#include <thread>

class A {
public:
    int i;
    A(int _i) : i(_i) {}
    A(const A& other) {
        i = other.i + 2;
        std::cout << i << std::endl;
    }
    void fun(int x) {
        std::cout << "i + x = " << i + x << std::endl;
    }
};

int main() {
    A a(42);

    std::thread t_1(&A::fun, &a, 20);
    std::thread t_2(&A::fun, a, 20);

    t_1.join();
    t_2.join();
}
  • 如果要为参数是 move-only 类型的函数创建线程,则需要使用std::move()传入参数
    • 因为thread会构造一个参数类型的临时对象传入,如果参数只能移动构造的话,需要用move把变量变为右值传入
#include <iostream>
#include <thread>

void fun(std::unique_ptr<int> x) {
    std::cout << *x << std::endl;
}

int main() {
    std::unique_ptr<int> p = std::make_unique<int>(10);

    std::thread t(fun, std::move(p));
    t.join();

    if (p == nullptr) std::cout << "p = nullptr" << std::endl;
}

2.3 转移所有权

  • threadmove-only 类型,不能拷贝,只能通过移动转移所有权,但不能转移所有权到 joinable == true 的线程,即如果还没执行完手上的线程,无法接受其他线程的所有权
#include <iostream>
#include <thread>
#include <cassert>

using namespace std;

void f() {}
void g() {}

int main() {
    thread a(f);
    thread b(move(a));

    //断言 a不可汇入 b可汇入
    //即a没有在运行 b还在运行
    assert(!a.joinable());
    assert(b.joinable());

    a = std::thread{ g };

    assert(a.joinable());
    assert(b.joinable());

    // a = std::move(b);  报错 因为a还没有执行完毕,joinable = true
    a.join();
    a = std::move(b);

    assert(a.joinable());
    assert(!b.joinable());

    a.join();
}
  • 移动操作同样适用于支持移动的容器 vector
#include <iostream>
#include <thread>
#include <vector>

using namespace std;

int main() {
    vector<thread> threads;

    for (int i = 0; i < 10; i++) {
        threads.emplace_back([] {});
    }

    for (auto i = threads.begin(); i != threads.end(); i++) {
        i->join();
    }
}
  • thread可以作为函数返回值
#include <thread>
using namespace std;

thread f(){
    return thread([]{});
}

int main() {
    thread t(f());
    t.join();
}
  • thread可以作为函数参数,但是必须作为右值传入
#include <thread>
using namespace std;

void f(thread t){
    t.join();
}

int main() {
    thread t([]{}]);
    f(move(t));
}
  • 实现一个可以自动清理线程的类scoped_thread
#include <thread>
#include <stdexcept>
using namespace std;

class scoped_thread {
private:
    thread m_thread;
public:
	//只能以右值进行构造
    scoped_thread(thread _thread) : m_thread(move(_thread)) {
        if (!_thread.joinable()) {
            throw logic_error("no threads");
        }
    }
    //拷贝赋值以及拷贝构造设置为 delete
    scoped_thread(const scoped_thread& other) = delete;
    scoped_thread& operator=(const scoped_thread& other) = delete;

    //在析构函数中 回收线程
    ~scoped_thread() {
        m_thread.join();
    }
};

int main() {
    //对象在释放之前会先保证线程执行完毕
    scoped_thread thread(thread([] {}));
}
  • C++17标准给出一个建议,就是添加一个joining_thread的类型,这个类型与 std::thread 类似,不同是的添加 了析构函数,就类似于scoped_thread
  • 委员会成员们对此并没有达成统一共识,所以这个类没有添加入 C++17标准中(C++20仍旧对这种方式进行探讨,不过名称为 std::jthread )
#include <thread>
#include <iostream>
using namespace std;

class Jthread {
public:
    Jthread() noexcept = default;

    template <typename T, typename... Ts>
    explicit Jthread(T&& f, Ts... args) noexcept
        : _t(forward<T>(f), forward<Ts>(args)...) {}

    explicit Jthread(thread x) noexcept : _t(move(x)) {}

    Jthread(Jthread&& x) noexcept : _t(move(x._t)) {}

    Jthread& operator=(Jthread&& x) noexcept {
        if (joinable()) {
            join();
        }
        _t = move(x._t);
        return *this;
    }

    Jthread& operator=(thread x) noexcept {
        if (joinable()) {
            join();
        }
        _t = move(x);
        return *this;
    }

    ~Jthread() {
        if (joinable()) {
            join();
        }
    }


    bool joinable() const noexcept { return _t.joinable(); }
    void join() noexcept { _t.join(); }
    void detach() noexcept { _t.detach(); }

    void swap(Jthread&& x) noexcept { _t.swap(x._t); }

    thread::id get_id() { return _t.get_id(); }

    thread& as_thread() noexcept { return _t; }
    const thread& as_thread() const noexcept { return _t; }

private:
    thread _t;
};

void fun(int x, int y) noexcept {
    cout << x + y << endl;
}

int main() {
    Jthread t(fun, 1, 10);
}

2.4 查看硬件支持的线程数量

  • hardware_concurrency可以返回硬件支持的并发线程数
  • 例如,多核系统中, 返回值可以是CPU核芯的数量。返回值也仅仅是一个标识,当无法获取时,函数返回0。
#include <thread>
#include <iostream>
using namespace std;

int main() {
    int n = thread::hardware_concurrency();
    cout << n << " concurrent threads are supported." << endl;
}

2.5 线程标识

  • 线程标识为 std::thread::id 类型,可以通过两种方式进行检索。
    • 可以通过调用std::thread对象的成 员函数get_id()来直接获取。如果std::thread对象没有与任何执行线程相关联, get_id() 将返 回 std::thread::type 默认构造值,这个值表示“无线程”。
    • 当前线程中调 用 std::this_thread::get_id() (这个函数定义在头文件中)也可以获得线程标识。
#include <thread>
#include <iostream>
using namespace std;

thread::id master_thread_id = this_thread::get_id();

void fun() {
    if (this_thread::get_id() == master_thread_id) {
        cout << "master_thread" << endl;
    }
    else {
        cout << "Its not a master thread: " << this_thread::get_id() << endl;
    }
}

int main() {
    thread t(fun);
    t.join();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值