C++11 并发编程之std::thread

1、std::thread简介和常用函数

C++11之前,window和linux平台分别有各自的多线程标准。使用C++编写的多线程往往是依赖于特定平台的。

  • Window平台提供用于多线程创建和管理的win32 api;
  • Linux下则有POSIX多线程标准,Threads或Pthreads库提供的API可以在类Unix上运行;

在C++11新标准中,正式的为该语言引入了多线程概念。可以简单通过使用thread库,来管理多线程。thread库可以看做对不同平台多线程API的一层包装;

因此使用新标准提供的线程库编写的程序是跨平台的。

1.1 提供的构造函数:

  1. 默认构造函数                                         thread() noexcept; 
  2. 接受函数及其传递参数的构造函数      template  explicit thread(_Fn&& _Fx, _Args&&... _Ax)
  3. move构造函数                                       thread(thread&& _Other) noexcept;
  4. 拷贝构造函数                                         thread(const thread&) = delete;
  5. 拷贝赋值运算符                                     thread& operator=(const thread&) = delete;

其中拷贝构造函数和拷贝赋值运算符被禁用,意味着std::thread对象不能够被拷贝和赋值到别的thread对象;

默认构造函数构造一个空的thread对象,但是不表示任何线程;

接受参数的构造函数创建一个表示线程的对象,线程从传入的函数开始执行,该对象是joinable的;

move构造函数可以看做将一个thread对象对线程的控制权限转移到另一个thread对象;执行之后,传入的thread对象不表示任何线程;

int main()
{
    int arg = 0;
    std::thread t1;                        // t1 is not represent a thread
    std::thread t2(func1, arg + 1);     // pass to thread by value
    std::thread t3(func2, std::ref(arg));  // pass to thread by reference
    std::thread t4(std::move(t3));         // t4 is now running func2(). t3不再代表任何线程
    //t1.join()  Error!
    t2.join();
    //t3.join()  Error!
    t4.join();
}

1.2  join && detach

对于创建的线程,一般会在其销毁前调用join和detach函数;

弄清楚这两个函数的调用时机和意义,以及调用前后线程状态的变化非常重要。

  • join 会使当前线程阻塞,直到目标线程执行完毕;
    • 只有处于活动状态线程才能调用join,可以通过joinable()函数检查;
    • joinable() == true表示当前线程是活动线程,才可以调用join函数;
    • 默认构造函数创建的对象是joinable() == false;
    • join只能被调用一次,之后joinable就会变为false,表示线程执行完毕;
    • 调用 ternimate()的线程必须是 joinable() == false;
    • 如果线程不调用join()函数,即使执行完毕也是一个活动线程,即joinable() == true,依然可以调用join()函数;
  • detach 将thread对象及其表示的线程分离;
    • 调用detach表示thread对象和其表示的线程完全分离;
    • 分离之后的线程是不在受约束和管制,会单独执行,直到执行完毕释放资源,可以看做是一个daemon线程;
    • 分离之后thread对象不再表示任何线程;
    • 分离之后joinable() == false,即使还在执行;

join实例分析

int main() {
    thread t(tstart, "C++ 11 thread!");
    cout << t.joinable() << endl;
    if (t.joinable()) t.join();
    //t.detach(); Error
    cout << t.joinable() << endl;
    // t.join(); Error
    cout << "Main Function!" << endl;
    system("pause");
}

简单来说就是只有处于活动状态的线程才可以调用join,调用返回表示线程执行完毕,状态将变为joinable() == false.

只有joinable() == true的线程,也就是活动状态的线程才可以调用join和detach;所以不可join两次。

当线程既没有调用join也没有调用detach的时候,线程执行完毕状态依然是joinable() == true,那么当thread对象被销毁的时候,会调用terminate()。

1.3  获取线程ID

线程ID是一个线程的标识符,C++标准中提供两种方式获取线程ID;

  1. thread_obj.get_id();
  2. std::this_thread::get_id()

有一点需要注意,就是空thread对象,也就是不表示任何线程的thread obj调用get_id返回值为0;

此外当一个线程被detach或者joinable() == false时,调用get_id的返回结果也为0。

 

1.4 交换thread线程

除了上面介绍的detach可以分离thread对象及其所表示的线程,或者move到别的线程之外,还可以使用swap来交换两个thread对象表示的线程。

实例来看一下两个线程的交换:

int tstart(const string& tname) {
    cout << "Thread test! " << tname << endl;
    return 0;
}

int main() {
    thread t1(tstart, "C++ 11 thread_1!");
    thread t2(tstart, "C++ 11 thread_2!");
    cout << "current thread id: " << this_thread::get_id() << endl;
    cout << "before swap: "<< " thread_1 id: " << t1.get_id() << " thread_2 id: " << t2.get_id() << endl;
    t1.swap(t2);
    cout << "after swap: " << " thread_1 id: " << t1.get_id() << " thread_2 id: " << t2.get_id() << endl;
    //t.detach();
    t1.join();
    t2.join();
}

输出:

Thread test! C++ 11 thread_1!
Thread test! C++ 11 thread_2!
current thread id: 39308
before swap:  thread_1 id: 26240 thread_2 id: 37276
after swap:  thread_1 id: 37276 thread_2 id: 26240

其实交换的过程仅仅是互换了thread对象所持有的底层句柄;

 

2、std::thread在项目中的使用

2.1 线程同步

  • mutex是用来保证线程同步的,防止不同的线程同时操作同一个共享数据。

  • #include:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。

#include
#include
#include

using namespace std;

mutex m;
int cnt = 10;

void thread1() {
    while (cnt > 5){
        m.lock();
        if (cnt > 0) {
            --cnt;
            cout << cnt << endl;
        }
        m.unlock();
    }
}

void thread2() {
    while (cnt > 0) {
        m.lock();
        if (cnt > 0) {
            cnt -= 10;
            cout << cnt << endl;
        }
        m.unlock();
    }
}

int main(int argc, char* argv[]) {
    thread th1(thread1);   //实例化一个线程对象th1,该线程开始执行
    thread th2(thread2);
    th1.join();
    th2.join();
    cout << "main..." << endl;
    return 0;
}

注意:mutex是不安全的,当一个线程在解锁之前异常退出了,那么其它被阻塞的线程就无法继续下去

使用lock_guard则相对安全,它是基于作用域的,能够自解锁,当该对象创建时,它会像m.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程。

#include
#include
#include

using namespace std;

mutex m;
int cnt = 10;

void thread1() {
    while (cnt > 5){
        lock_guard lockGuard(m);
        if (cnt > 0) {
            --cnt;
            cout << cnt << endl;
        }
    }
}

void thread2() {
    while (cnt > 0) {
        lock_guard lockGuard(m);
        if (cnt > 0) {
            cnt -= 10;
            cout << cnt << endl;
        }
    }
}

int main(int argc, char* argv[]) {
    thread th1(thread1);   //实例化一个线程对象th1,该线程开始执行
    thread th2(thread2);
    th1.join();
    th2.join();
    cout << "main..." << endl;
    return 0;
}

 

2.2 为线程传递参数

#include  
using namespace std;  

void show(const char *str, const int id)  
{  
    cout << "线程 " << id + 1 << " :" << str << endl;  
}  

int main()  
{  
    thread t1(show, "hello cplusplus!", 0);  
    thread t2(show, "你好,C++!", 1);  
    thread t3(show, "hello!", 2);  
    return 0;  
}  

2.3 lambda表达式在thread中的应用

#include  
using namespace std;  
int main()  
{  
    auto fun = [](const char *str) {cout << str << endl; };  
    thread t1(fun, "hello world!");  
    thread t2(fun, "hello beijing!");  
    return 0;  
}  

输出:

hello world!  
hello beijing!

 

2.4 thread中的可变参数

#include  
#include  
using namespace std;  
int show(const char *fun, ...)  
{  
    va_list ap;//指针  
    va_start(ap, fun);//开始  
    vprintf(fun, ap);//调用  
    va_end(ap);  
    return 0;  
}  
int main()  
{  
    thread t1(show, "%s    %d    %c    %f", "hello world!", 100, 'A', 3.14159);  
    return 0;  
}  

输出:

hello world!    100    A    3.14159

 

你可能感兴趣的:(C++,c++,linux,面试)