C++11 多线程 笔记1

版权声明:所有的博客仅仅作为个人笔记使用!!!!!!! https://blog.csdn.net/qq_35976351/article/details/84061281

线程管理基础

C++11 所有的线程都封装在<thread>头文件中,使用命名空间std说明。

最简单的例子:

#include <iostream>
#include <thread>

void hello() {
    std::cout << "hello world !\n";
}

int main() {
    std::thread t(hello);  // 定义一个线程,注意是直接构造执行的
    t.join();              // 在这里进行等待
    return 0;
}

每个线程都必须有一个初始函数,新线程在函数中执行。join()的作用是等待线程执行完毕,然后向下执行。该例子中,如果没有join,那么可能出现主线程执行完毕,子线程还未完成,出现异常。

C++线程库启动线程可以归结为构造一个std::thread对象,使用可调用类型进行构造。传入带有函数调用符类型的参数实例,可以用来替代默认的构造函数。

joindetach的区别:

  • join是用于等待线程执行完毕后,再向下执行
  • detach是声明是线程再后台执行,不必等待线程执行完毕,主线程可以继续向下执行。而且此时不能有其他的std::thread对象能引用它。
  • 两者都不能对没有执行的线程进行操作,保险的做法是先进行joinable的判断,然后调用函数。

线程使用的注意事项:尽量不要在线程中访问局部变量的引用或者指针,保险的做法是复制数据到线程中。

#include <iostream>
#include <thread>

void do_something(int i) {/*do some thing here*/}

struct func {
    int& i;  
    func(int& i_): i(i_) {}   // 注意都是引用类型!!!!
    void operator()() {
        for(unsigned j = 0; j < 1000000; ++j) {
            do_something(i);
        }
    }
};

void f() {
    int some_local_state = 0;
    func my_func(some_local_state);
    std::thread t(my_func);
    try {
        // do something in current thread
    } catch(...) {  // 捕获所有类型的异常
        t.join();   // 如果执行出现异常,那么在这里合并线程
        throw;      
    }
    t.join();
}

int main(){
    f();
}

使用异常的方式,使得f函数过于繁琐,可以利用析构函数的方式简化:

#include <iostream>
#include <thread>

void do_something(int i) {
    /*do some thing here*/
    for(int j = 0; j < 10; ++j) {
        i = j;
    }
}

struct func {
    int& i;
    func(int& i_): i(i_) {}
    void operator()() {
        for(unsigned j = 0; j < 1000000; ++j) {
            do_something(i);
        }
    }
};

class thread_guard {
  public:
    explicit thread_guard(std::thread& t_): t(t_) {}
    ~thread_guard() {
        if(t.joinable()) {
            t.join();
        }
    }
    thread_guard(thread_guard const&) = delete;
    thread_guard& operator=(thread_guard const&) = delete;
    std::thread& t;
};

void f() {
    int some_local_state = 0;
    func my_func(some_local_state);
    std::thread t(my_func);
    thread_guard g(t);
    // do something in current thread
}

int main() {
    f();
}

f函数中,当函数执行完成时,需要销毁局部变量。因为局部变量时存放在栈内存中的,所以首先要销毁g,即调用g的构造函数,此时主线程需要进行joinable的判断,来确定子线程是否执行完毕,未结束则等待到结束。

向线程传递参数

基本方式很简单,声明函数后,依次添加对应的参数即可,类似于std::bind

void foo(int i, std::string const& s) {
    std::cout << "i= " << i << ", s= " << s << std::endl;
}

int main() {
    std::thread t(foo, 3, "hello world !");
    if(t.joinable()) {
        t.join();
    }
    return 0;
}
/*
输出结果:
i= 3, s= hello world !
*/

一般来说,如果无特殊需求,而且复制的开销比较小,那么应该给每个线程一个局部变量的复制。但是存在两个特殊情况:传递指针和传递引用。

一般来说,传递指针时要注意不要出现指针悬空的情况。传递引用的时候要使用std::ref,这和使用std::bind的时候类似。

void foo(int i, std::string& s) {
    std::cout << "i= " << i << std::endl;
    s = " " + s;
    std::cout << "s= " << s << std::endl;
}

int main() {
    std::string str("hello world");
    std::thread t(foo, 3, std::ref(str));  // 一定要注意std::ref的使用!!!
    if(t.joinable()) {
        t.join();
    }
    std::cout << "main thread s= " << str << std::endl;
    return 0;
}

如果要使用类成员函数,则需要单独声明,使用方式如下:

class X {
  public:
    void foo(int i) {
        std::cout << "i= " << i << std::endl;
    }
};

int main() {
    X my_x;
    std::thread t(&X::foo, &my_x, 8);
    if(t.joinable()) {
        t.join();
    }
    return 0;
}

仅仅是添加上域作用符和函数地址声明符,参数从第三个开始。

也可以

也可以使用移动std::move的方式进行参数传递。

转移线程所有权

线程没有拷贝操作!!! ,所有权的转移都是通过std::move实现的。这种类型的资源是不能直接赋值的,没有意义,只能直接创建。

线程的所有权可以在函数外进行转移。

std::thread f() {
    void some_function();
    return std::thread(some_function);
}

std::thread g() {
    void some_other_function(int);
    std::thread t(some_other_function, 42);
    return t;
}

编译器有ROV,不用担心出现多余的拷贝,这里返回值就是相当于移动操作。

所有权可以在函数内部传递,同样的也可以作为函数的参数,不过要使用std::move进行操作。

void f(std::thread t);
void g() {
    void some_function();
    f(std: thread(some_function));   // 可以直接在参数处进行构造
    std::thread t(some_function);    // 直接进行移动操作
    f(std::move(t));
}

可以使用类构造的方式,直接传入线程,而不是各个参数。这样,可以更加简化操作。

#include <iostream>
#include <thread>

void do_something(int i) {
    /*do some thing here*/
    for(int j = 0; j < 10; ++j) {
        i = j;
    }
}

struct func {
    int& i;
    func(int& i_): i(i_) {}
    void operator()() {
        for(unsigned j = 0; j < 1000000; ++j) {
            do_something(i);
        }
    }
};

class scoped_thread {
  public:
    explicit scoped_thread(std::thread t_): t(std::move(t_)) {
        if(!t.joinable()) {
            throw std::logic_error("No thread");
        }
    }
    ~scoped_thread() {
        t.join();
    }
    scoped_thread(scoped_thread const&) = delete;
    scoped_thread& operator=(scoped_thread const&) = delete;
  private:
    std::thread t;
};

void f() {
    int some_local_state;
    scoped_thread t(std::thread(func(some_local_state)));
    // do something in current thread
}

int main() {
    f();
}

可以使用线程容器存放线程,前提是容器支持移动操作。这里以std::vector为例子:

#include <thread>
#include <vector>
#include <algorithm>

void do_work(){}

void f() {
    std::vector<std::thread> threads;
    for(unsigned i = 0; i < 20; ++i) {
        threads.push_back(std::thread(do_work));
    }
    std::for_each(threads.begin(), threads.end(),
                  std::mem_fn(&std::thread::join));

补充说明:std::mem_fn相当于一个函数适配器,返回适配后的可调用的函数对象,这里相当于对每个线程都执行一次join操作。这种方式相当于创建了一个线程组。

运行时线程的数量

std::thread::hardware_concurrency()函数返回CPU核心的数量或者是一个程序中最多能同时并发的函数数量。

给出一个多线程版本的std::accumulate函数。

#include <thread>
#include <algorithm>

// 每个线程内部进行累加操作
template<typename Iterator, typename T>
struct accumulate_block {
    void operator()(Iterator first, Iterator last, T& result) {
        result = std::accumulate(first, last, result);
    }
};

// 线程并行的函数
template<typename Iterator, typename T>
T parallel_accumulate(Iterator first, Iterator last, T init) {
    auto const length = std::distance(first, last);  // 任务的数量

    if(!length)  // 没有任务,则返回初始值
        return init;

	// 每个线程最小的任务数量
    unsigned long const min_per_thread = 25;  
    // 系统最多启动线程的个数,这种操作是防止出现0个线程的情况,同时让每个线程平均分配任务
    unsigned long const max_threads = (length + min_per_thread - 1) / min_per_thread;

    unsigned long const hardware_threads = std::thread::hardware_concurrency();
    // 最多线程不能超过任务数量,也不能超过系统支持线程的数量
    unsigned long const num_threads = std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads);
    // 每个线程实际分配的任务个数
    unsigned long const block_size = length / num_threads;
	// 存放累加结果
    std::vector<T>results(num_threads); 
    // 存放线程,注意-1是因为不存放主线程
    std::vector<std::thread>threads(num_threads - 1); 

    Iterator block_start = first;
    for(unsigned long i = 0; i < (num_threads - 1); ++i) {
        Iterator block_end = block_start;
        // 每次累加任务个数
        std::advance(block_end, block_size);
        // 线程内部执行自己所属区块的累加,结果存储到对应的results中,注意引用!!
        threads[i] = std::thread(accumulate_block<Iterator, T>(),
                                 block_start, block_end, std::ref(results[i]));
        block_end = block_start;
    }
    // 处理可能分配不均匀的块
    accumulate_block<Iterator, T>()(block_start, last, results[num_threads - 1]);
    // 等待所有的线程结束
    std::for_each(threads.begin(), threads.end(),
                  std::mem_fn(&std::thread::join));
    // 累加所有的结果,并返回
    return std::accumulate(results.begin(), results.end(), init);
}

补充:std::accumulate()是累加函数,std::advance()是迭代器移动函数,必须是前向或者双向迭代器才可以。

注意:不能从线程中直接返回一个值,在这里采取引用的方式进行解决。

识别线程

C++中,线程的标识符类型是std::thread::id。使用std::threadget_id()成员直接获取。一般来说,这种方式是用来对线程进行检测的,以判断是否需要进行有关的操作。

猜你喜欢

转载自blog.csdn.net/qq_35976351/article/details/84061281
今日推荐