zoukankan      html  css  js  c++  java
  • c++11 线程:让你的多线程任务更轻松

     

     

    介绍

    本文旨在帮助有经验的Win32程序员来了解c++ 11线程库及同步对象 和 Win32线程及同步对象之间的区别和相似之处。

    在Win32中,所有的同步对象句柄(HANDLE)是全局句柄.它们可以被共享,甚至可以在进程间复制。在C++11中,所有的同步对象都是栈(stack)对象,这意味着它们必须是可“分离(detached)”的(如果支持“分离”的话)以便能够被栈框架(stack frame)所析构。如果大量对象应该分离而你没有,那么它们便会无法实现自己的行动,而毁掉你的原本计划。(译者注:在pthread中,线程有joinable和unjoinable之分,具有joinable的线程在线程结束时,不会清空该线程所占用的栈空间,通常的做法是在pthrea_create创建线程后,再调用pthread_join(有点waitforsingleobject的意思)才会清空,而unjoinable的属性的线程在线程结束时,就会自动清空所占用空间)

    所有的c++11同步对象都有一个native_handle()成员,它返回具体实现句柄(在win32,它就是一个handle)

    在我的所有例子,我给出了win32伪代码。祝你愉快!

    小熊猫大暴走
    小熊猫大暴走
    翻译于 2年前

    2人顶

     

     翻译的不错哦!

    背景知识

    ox0000000.木有 :D。我也是c++11线程的新手。你需要自己去了解win32同步相关知识。这里可能不是合适的同步技术的教程,而是一个C++11机制的快速引导,以便对你所指定的计划有所帮助。

    简单成就完美

    一个简单例子:启动一个线程,然后等它结束:

    1
    2
    3
    4
    5
    6
    7
    8
    void foo()
      {
      }
    void func()
      {
      std::thread t(foo); // Starts. Equal to CreateThread.
      t.join();  // Equal to WaitForSingleObject to the thread handle.
      }

    与win32线程不同,你可以在这里传递参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void foo(int x,int y)
      {
      // x = 4, y = 5.
      }
    void func()
      {
      std::thread t(foo,4,5); // Acceptable.
      t.join(); 
      }

    这样,通过传递‘this’指针给std::thread让成员函数成为一个线程,变成了一件很简单的事情.如果std::thread得以析构,而你没有调用join(),它将会异常终止。脱离c++封装运行线程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void foo()
      {
      }
    void func()
      {
          std::thread t(foo);
          // 在这里已经调用了detach方法,c++对象从win32对象中脱离出来,如果此时还调用join方法,就会抛出std::system_error()
          t.detach();
      }

    除了join(),detach()方法,还有joinable(),get_id(),sleep_for(),sleep_until().它们都是自解释的,很好理解。

    小熊猫大暴走
    小熊猫大暴走
    翻译于 2年前

    1人顶

     

     翻译的不错哦!

    使用互斥(Mutex)

    std::mutex与win32的临界区(cirtical section)很类似。lock()如同EnterCriticalSectionunlock如同LeaveCriticalSectiontry_lock则像TryEnterCriticalSection

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    std::mutex m;
    int j = 0;
    void foo()
      {
      m.lock();        // 进入临界区域
      j++;
      m.unlock();      // 离开
      }
    void func()
      {
      std::thread t1(foo);
      std::thread t2(foo);
      t1.join();
      t2.join();
     // j = 2;
    }
    如上,你在lock一个 std::mutex 对象之后必须解锁(unlock)。如果你已经对其加锁,你不能再次lock。这与win32 不同,如果你已经在临界区(critical section)里,再次 EnterCriticalSection不会失败,但是会增加一个计数。

    嗨,不要走开哦。前面提到不能对std::mutex重复lock。这里有std::recursive_mutex(谁发明的这名字),它的行为则与临界区(critical section)相似,可以重复lock。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::recursive_mutex m;
    void foo()
      {
      m.lock();
      m.lock(); // now valid
      j++;
      m.unlock();
      m.unlock(); // don't forget!
      }
    此外,还有 std::timed_mutex, std::recursive_timed_mutex,他们提供 try_lock_fortry_lock_until方法,允许你等待一个lock,直到超时,或者达到定义的时间。
    小熊猫大暴走
    小熊猫大暴走
    翻译于 2年前

    3人顶

     

     翻译的不错哦!

    C++11 Thread的线程本地存储(Thread Local Storage)

    TLS(thread local storage)类似,该功能允许你声明一个带有thread_local的声明符的变量。这意味着,每一个线程都有自己的该全局变量的实例(instance),该实例的变量名就是全局变量名称。

    以前:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int j = 0;
    void foo()
      {
      m.lock();
      j++;
      m.unlock();
      }
    void func()
      {
      j = 0;
      std::thread t1(foo);
      std::thread t2(foo);
      t1.join();
      t2.join();
     // j = 2;
    }
    现在我们看:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    thread_local int j = 0;
    void foo()
      {
      m.lock();
      j++; // j is now 1, no matter the thread. j is local to this thread.
      m.unlock();
      }
    void func()
      {
      j = 0;
      std::thread t1(foo);
      std::thread t2(foo);
      t1.join();
      t2.join();
     // j still 0. The other "j"s were local to the threads
    }
    然而,Visual Studio还不支持 tls(我想这里的tls应该是c++11 thread的tls)
    小熊猫大暴走
    小熊猫大暴走
    翻译于 2年前

    1人顶

     

     翻译的不错哦!

    神秘的变量

    条件变量(Conditional variables)是能够使线程等待特定条件的对象。在window系统里,这些对象属于用户模式(usr-mode),因而不能被其他进程所共享。在window系统,条件变量与临界区(critical section)有关,用来获取或者释放一个锁。std::condition_variablestd::mutex联用,也是这个原因。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    std::condition_variable c;
    // 我们使用mutex而不是recursive_mutex是因为该锁需要一次性获取和释放
    std::mutex mu; // We use a mutex rather than a recursive_mutex because the lock has to be acquired only and exactly once.
    void foo5()
    {
       std::unique_lock lock(mu); // Lock the mutex
       c.notify_one(); // WakeConditionVariable. It also releases the unique lock  等待条件变量,它也会释放unque lock
    }
    void func5()
    {
       std::unique_lock lock(mu); // Lock the mutex
       std::thread t1(foo5);
       // 等价与SleepConditionVariableCS,它解锁mutex 变量nu,并允许foo5来加锁
       c.wait(lock); // Equal to SleepConditionVariableCS. This unlocks the mutex mu and allows foo5 to lock it
       t1.join();
    }

    这并不像看上去那么简单。c.wait() 可能会返回,即使c.notify_one()没有被调用(已知的这种情况是spurious wakeup- http://msdn.microsoft.com/en-us/library/windows/desktop/ms686301(v=vs.85).aspx)。通常,在Vista及以上操作系统,条件变量才被支持。

    小熊猫大暴走
    小熊猫大暴走
    翻译于 2年前

    0人顶

     

     翻译的不错哦!

    未来的承诺

    设想这样的情况,你希望一个线程做一些事情,然后返回你一个结果。同时,你在做一些其他的工作,该工作也许会也许不会花费你一点时间。你希望在某个特定的时间获取那个线程的结果。

    在win32中,你可以这样

    • 用CreateThread启动线程
    • 在线程里,启动任务,当准备完毕后发送一个事件(event),并把结果放在全局变量里。
    • 在主函数里(main)做其它的事情,然后在你想要结果的地方,调用WaitForSingleObject

    在c++11,这个可以轻松被std::future实现,然后返回任何类型,因为它是一个模板。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int GetMyAnswer()
       {
       return 10;
       }
    int main()
      {
      std::future<int> GetAnAnswer = std::async(GetMyAnswer);  // GetMyAnswer starts background execution
      int answer = GetAnAnswer.get(); // answer = 10;
      // If GetMyAnswer has finished, this call returns immediately.
      // If not, it waits for the thread to finish.
      }
    你也可以使用 std::promise。该对象可以提供一些 std::future以后需求的特性。如果在任何东西放入承诺(promise)之前你调用 std::future::get(),将会导致等待,直到承诺值(promised value)出现。如果std::promise::set_exception()被调用, std::future::get()则会抛出异常。如果 std::promise销毁了,而你调用std::future::get(),你将会产生 broken_promise 异常。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    std::promise<int> sex;
    void foo()
      {
      // do stuff
      // 在下面的调用之后,future::get()将会返回该值
      sex.set_value(1); // After this call, future::get() will return this value.
         
      // 调用之后,future::get()将会抛出这个异常
      sex.set_exception(std::make_exception_ptr(std::runtime_error("TEST")));
      }
    int main()
      {
      future<int> makesex = sex.get_future();
      std::thread t(foo);
       
      // do stuff
      try
        {
        makesex.get();
        hurray();
        }
      catch(...)
        {
        // She dumped us :(
        }
      }
    小熊猫大暴走
    小熊猫大暴走
    翻译于 2年前

    0人顶

     

     翻译的不错哦!

    代码

    附上的代码包含所有上述我的所述。可以在visualstudio 2012 CTP版本的编译器下编译成功(除了tls机制)。(代码地址:http://www.codeproject.com/KB/cpp/540912/c11threads.zip)

    还有什么

    还有很多值得包括的事情,如:
    • 信号量(Semaphores)
    • 命名对象 (Named objects)
    • 进程共享对象 (Shareable objects across processes.)
    • ...

    你应该做什么呢?通常当编写新的代码,如果足够适用,尽量选择C++标准。对于已存在的代码,我尽量保持使用win32调用,当需要移植它们到另外的平台,我则会用c++11函数来实现CreateThread、SetEvent 等等。

    祝你好运

    小熊猫大暴走
    小熊猫大暴走
    翻译于 2年前

    0人顶

     

     翻译的不错哦!

  • 相关阅读:
    团队冲刺0202
    团队冲刺0201
    第十五周
    第十四周博客
    十三周总结
    软件设计模式13
    软件设计模式12
    软件构造4
    软件设计模式11
    软件设计模式10
  • 原文地址:https://www.cnblogs.com/lvdongjie/p/4487825.html
Copyright © 2011-2022 走看看