C++11之多线程初探

来源:互联网 发布:纽约跑跑美国代购 知乎 编辑:程序博客网 时间:2024/06/16 00:05

C++11在标准库中加入了std::thread以支持多线程编程,主要要用的头文件是:

  • <thread>:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
  • <mutex>:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard,std::unique_lock, 以及其他的类型和函数。

1.创建和执行线程

#include <iostream>#include <thread>using namespace std;void MythreadA(){    for (int i = 0; i < 10; i++)    {        cout << "MyThreadA executing!" << endl;    }}void MythreadB(int n){    for (int i = 0; i < n; i++)    {        cout << "MyThreadB " << i << endl;    }}int main(){    thread t1(MythreadA);     //用方法MythreadA()实例化线程t1    thread t2(MythreadB,10);  //用方法MythreadB()实例化线程t2并传入参数    t1.join();  //等待子线程t1执行完    t2.join();  //等待子线程t2执行完    getchar();    return 0;}

  创建一个线程需要绑定一个方法,该方法是该线程执行的内容。在实例化一个线程时,可以在线程函数名之后紧接着传入其参数。实例化一个线程之后实际上该线程机就立刻开始执行了。

  在实例化线程之后,调用了thread::join()方法,该方法作用是让主线程等待直到该子线程执行结束。如果不调用该方法,则主线程main()并没有停止脚步,仍然继续执行然后退出,此时线程对象还是joinable的,线程仍然存在但指向它的线程对象已经销毁,会抛出异常。

  如果不想等待子线程,可以在主线程里面执行thread::detach()将子线程从主线程里分离,子线程执行完成后会自己释放掉资源。分离后的线程,主线程将对它没有控制权。

2. 在类中创建和执行线程

  考虑一个多线程售票的问题,在两个地点A和B分别售票。用程序实现是在主线程中创建两个线程A和B,在线程A和B中分别售票,直到所有票售完结束线程A和B。

在头文件ThreadTest.h中定义一个线程测试类:

#include <iostream>#include <thread>class ThreadTest{private:    int m_tickets;public:    ThreadTest(int tickets);    void Run();    void MyThreadA();    void MyThreadB();};

在源文件ThreadTest.cpp中实现该类:

#include "ThreadTest.h"#include <windows.h>using namespace std;ThreadTest::ThreadTest(int ticket){    m_tickets = ticket;}void ThreadTest::MyThreadA(){    while (true)    {        if (m_tickets > 0)        {            cout << "A Sell " << m_tickets-- << endl;//输出售票,每次减1                    //Sleep(100);        }        else         {            break;        }    }}void ThreadTest::MyThreadB(){    while (true)    {        if (m_tickets > 0)        {            cout << "B Sell " << m_tickets-- << endl;//输出售票,每次减1              //Sleep(100);        }        else         {            break;        }    }}void ThreadTest::Run(){    thread tA(&ThreadTest::MyThreadA, this);    thread tB(&ThreadTest::MyThreadB, this);    tA.join();    tB.join();    cout << "子线程结束运行" << endl;    cout << "主线程结束" << endl;}

在ThreadMian.cpp中使用ThreadTest类:

#include "ThreadTest.h"int main(){    ThreadTest tt(100);    tt.Run();    return 0;}

  实际上输出的结果每一次都不一样,cout << "A Sell " << m_tickets-- << endl;cout << "B Sell " << m_tickets-- << endl;这两句代码实际上执行到一半都有可能会进行时间片切换而转去执行其他线程,所以输出的结果看上去有些混乱,甚至可能会出现同一张票买两次的情况。

3.使用互斥量mutex

  为了使一个线程或者其中的一段代码能够执行完,使用互斥对象是一个比较好的方法。

在头文件ThreadTest.h中定义一个包含互斥锁的线程测试类:

#include <iostream>#include <thread>#include <mutex>class ThreadTest{private:    int m_tickets;    std::mutex m_mutex;public:    ThreadTest(int tickets);    void Run();    void MyThreadA();    void MyThreadB();};

修改源文件ThreadTest.cpp中的MyThreadA()和MyThreadB():

void ThreadTest::MyThreadA(){    while (true)    {        m_mutex.lock();   //加锁        if (m_tickets > 0)        {            cout << "A Sell " << m_tickets--<<endl;//输出售票,每次减1                      m_mutex.unlock();   //解锁            Sleep(100);        }        else         {            m_mutex.unlock();  //解锁            break;        }    }}void ThreadTest::MyThreadB(){    while (true)    {        m_mutex.lock();    //加锁        if (m_tickets > 0)        {            cout << "B Sell " << m_tickets--<<endl;//输出售票,每次减1              m_mutex.unlock();   //解锁            Sleep(100);        }        else         {            m_mutex.unlock();   //解锁            break;        }    }}

  这样票数就可以正常的依次递减了,且cout语句也可以完整的执行完毕。但是依然存在一个问题,即某一个线程lock()之后,如果其出现异常导致中途退出了该线程或者程序中断那么该互斥量就一直得不到解锁,而其它使用该互斥量加锁的线程就会一直处于等待状态。

4.使用lock_guard

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

void ThreadTest::MyThreadA(){    while (true)    {        lock_guard<mutex> lockGuard(m_mutex);        if (m_tickets > 0)        {            if (m_tickets <= 50)               {                break;   //强制结束该线程            }            cout << "A Sell " << m_tickets-- << endl;//输出售票,每次减1         }        else        {            break;        }    }}void ThreadTest::MyThreadB(){    while (true)    {        lock_guard<mutex> lockGuard(m_mutex);        if (m_tickets > 0)        {            cout << "B Sell " << m_tickets-- << endl;//输出售票,每次减1          }        else        {            break;        }    }}

  这样即使某一个线程中途退出也不会使系统的其他线程一直处于等待状态。

5.原子操作

  C++11,引入了原子操作的概念,并通过<atomic>这个新的头文件提供了多种原子操作数据类型,例如,atomic_bool,atomic_int等等,如果我们在多个线程中对这些类型的共享资源进行操作,编译器将保证这些操作都是原子性的,也就是说,确保任意时刻只有一个线程对这个资源进行访问,编译器将保证,多个线程访问这个共享资源的正确性。从而避免了锁的使用,提高了效率。

  <atomic>头文件中为所有的基础类型Type都定义了相应的原子类型atomic<Type>atomic_Type。当然也可以使用该类模板用自定义的类定义相应的原子操作类,该类必须是trivially copyable type。原子操作的实现跟普通数据类型类似,它能够在保证结果正确的前提下,提供比mutex等锁机制更好的性能。

  <atomic>头文件中定义了一种特殊的自旋锁atomic_flag,和其他的原子数据类型(包括atomic_bool)不同的是,他是锁无关(lock-free)的一种类型,即线程对它的访问是不需要加锁的,因此他也没有其他的原子类型的读写操作(load(),store())、运算符操作等。取而代之的是另外两个原子操作的函数test_and_set()和clear()。使用atomic_flag也可以实现mutex类似的功能。atomic_flag::test_and_set()检查flag是否被设置,若被设置直接返回true,若没有设置则设置flag为true后再返回false;atomic_clear()清除flag标志即flag=false。

使用atomic_flag实现互斥的ThreadTest.h如下:

#include <iostream>#include <thread>#include <atomic>class ThreadTest{private:    int m_tickets;    std::atomic_flag m_lock = ATOMIC_FLAG_INIT;   //初始化atomic_flagpublic:    ThreadTest(int tickets);    void Run();    void MyThreadA();    void MyThreadB();};

ThreadTest.cpp如下:

#include "ThreadTest.h"#include <windows.h>using namespace std;//atomic_int g_ticket1 = 0;//atomic<int> g_ticket2 = { 0 };ThreadTest::ThreadTest(int ticket){    m_tickets = ticket;}void ThreadTest::MyThreadA(){    while (true)    {        if (!m_lock.test_and_set())  //尝试加锁        {            if (m_tickets > 0)            {                cout << "A Sell " << m_tickets-- << endl;//输出售票,每次减1                    m_lock.clear();      //解锁                //Sleep(100);            }            else            {                m_lock.clear();      //解锁                break;            }        }    }}void ThreadTest::MyThreadB(){    while (true)    {        if (!m_lock.test_and_set())  //尝试加锁            {            if (m_tickets > 0)            {                cout << "B Sell " << m_tickets-- << endl;//输出售票,每次减1                    m_lock.clear();      //解锁                //Sleep(100);            }            else            {                m_lock.clear();      //解锁                break;            }        }    }}void ThreadTest::Run(){    thread tA(&ThreadTest::MyThreadA, this);    thread tB(&ThreadTest::MyThreadB, this);    tA.join();    tB.join();    cout << "子线程结束运行" << endl;    cout << "主线程结束" << endl;}
原创粉丝点击