C++11 thread

来源:互联网 发布:淘宝怎么查看消费总额 编辑:程序博客网 时间:2024/06/05 10:45
windows系统中,需要vs2010+sp1或vs2012才支持。


1.线程的创建
C++11线程类std::thread,头文件include <thread>
首先,看一个最简单的例子:

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. void my_thread()
  2. {
  3. puts("hello, world");
  4. }
  5. int main(int argc,char *argv[])
  6. {
  7. std::thread t(my_thread);
  8. t.join();
  9. system("pause");
  10. return 0;
  11. }

实例化一个线程对象t,参数my_thread是一个函数,在线程创建完成后将被执行,
t.join()等待子线程my_thread执行完之后,主线程才可以继续执行下去,此时主线程会
释放掉执行完后的子线程资源。


当然,如果不想等待子线程,可以在主线程里面执行t.detach()将子线程从主线程里分离,
子线程执行完成后会自己释放掉资源。分离后的线程,主线程将对它没有控制权了。
相对于以前使用过的beginthread传多个参数需要传入struct地址,
boost::thread传参需要bind,std::thread传参真的非常方便,而且可读性也很好。
下面例子在实例化线程对象的时候,在线程函数my_thread后面紧接着传入两个参数。
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #include <iostream>
  2. #include <stdlib.h>
  3. #include <thread>
  4. #include <string>
  5. void my_thread(int num,const std::string& str)
  6. {
  7. std::cout << "num:" << num << ",name:" << str << std::endl;
  8. }
  9. int main(int argc,char *argv[])
  10. {
  11. int num = 1234;
  12. std::string str = "tujiaw";
  13. std::thread t(my_thread, num, str);
  14. t.detach();
  15. system("pause");
  16. return 0;
  17. }


2.互斥量
多个线程同时访问共享资源的时候需要需要用到互斥量,当一个线程锁住了互斥量后,其他线程必须等待这个互斥量解锁后才能访问它。thread提供了四种不同的互斥量:
独占式互斥量non-recursive (std::mutex)
递归式互斥量recursive (std::recursive_mutex)
允许超时的独占式互斥量non-recursive that allows timeouts on the lock functions(std::timed_mutex)
允许超时的递归式互斥量recursive mutex that allows timeouts on the lock functions (std::recursive_timed_mutex)


独占式互斥量
独占式互斥量加解锁是成对的,同一个线程内独占式互斥量在没有解锁的情况下,再次对它进行加锁这是不对的,会得到一个未定义行为。
如果你想thread1输出10次10,thread2输出10次20,如果你想看到一个正确的显示效果,下面程序是做不到的,因为在thread1输出的时候,
thread2也会执行,输出的结果看起来有点乱(std::cout不是线程安全的),所以我们需要在它们访问共享资源的时候使用互斥量加锁。打开代码里面的三行注释就可以得到正确的结果了。在线程1中std::mutex使用成员函数lock加锁unlock解锁,看起来工作的很好,但这样是不安全的,你得始终记住lock之后一定要unlock,但是如果在它们中间出现了异常或者线程直接退出了unlock就没有执行,因为这个互斥量是独占式的,所以在thread1没有解锁之前,其他使用这个互斥量加锁的线程会一直处于等待状态得不到执行。lock_guard模板类使用RAII手法封装互斥量,在实例化对象的时候帮你加锁,并且能保证在离开作用域的时候自动解锁,所以你应该用lock_guard来帮你加解锁。
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #include <iostream>
  2. #include <stdlib.h>
  3. #include <thread>
  4. #include <string>
  5. #include <mutex>
  6. int g_num = 0;
  7. std::mutex g_mutex;
  8. void thread1()
  9. {
  10. //g_mutex.lock();
  11. g_num = 10;
  12. for (int i=0; i<10; i++){
  13. std::cout << "thread1:" << g_num << std::endl;
  14. }
  15. //g_mutex.unlock();
  16. }
  17. void thread2()
  18. {
  19. //std::lock_guard<std::mutex> lg(g_mutex);
  20. g_num = 20;
  21. for (int i=0; i<10; i++){
  22. std::cout << "thread2:" << g_num << std::endl;
  23. }
  24. }
  25. int main(int argc,char *argv[])
  26. {
  27. std::thread t1(thread1);
  28. std::thread t2(thread2);
  29. t1.join();
  30. t2.join();
  31. system("pause");
  32. return 0;
  33. }



递归式互斥量
与独占式互斥量不同的是,同一个线程内在互斥量没有解锁的情况下可以再次进行加锁,不过他们的加解锁次数需要一致,递归式互斥量我们平时可能用得比较少些。


允许超时的互斥量
如果线程1对共享资源的访问时间比较长,这时线程2可能等不了那么久,所以设置一个超时时间,在超时时间内如果线程1中的互斥量还没有解锁,线程2就不等了,继续往下执行。
lock_guard只是提供了对互斥量最基本的加解锁封装,而unique_lock提供了多种构造方法,使用起来更加灵活,对于允许超时的互斥量需要使用unnique_lock来包装。
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. std::timed_mutex g_timed_mutex;
  2. void thread1()
  3. {
  4. std::unique_lock<std::timed_mutex> tl(g_timed_mutex);
  5. ::Sleep(3000); // 睡眠3秒
  6. puts("thread1");
  7. }
  8. void thread2()
  9. {
  10. std::unique_lock<std::timed_mutex> tl(g_timed_mutex, std::chrono::milliseconds(1000));// 超时时间1秒
  11. puts("thread2");
  12. }
  13. int main(int argc,char *argv[])
  14. {
  15. std::thread t1(thread1);
  16. ::Sleep(100); // 让线程1先启动
  17. std::thread t2(thread2);
  18. t1.join();
  19. t2.join();
  20. system("pause");
  21. return 0;
  22. }


注意死锁
有时,一个操作需要对一个以上的mutex加锁,这时请注意了,这样很可能造成死锁。
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. struct Widget
  2. {
  3. std::mutex mutex_;
  4. std::string str_;
  5. };
  6. void foo(Widget& w1, Widget& w2)
  7. {
  8. std::unique_lock<std::mutex> t1(w1.mutex_);
  9. std::unique_lock<std::mutex> t2(w2.mutex_);
  10. // do something
  11. }
  12. Widget g_w1, g_w2;

当一个线程调用foo(g_w1, g_w2),另外一个线程调用foo(g_w2, g_w1)的时候,
线程1: 线程2:
w1.mutex_.lock ...
... w2.mutex_.lock
... ...
w2.mutex_.lock等待 ...
w1.mutex_lock等待
可能的执行顺序:
线程1中的w1上锁;
线程2中的w2上锁;
线程1中的w2上锁,此时由于w2已经在线程2中上过锁了,所以必须等待;
线程2中的w1上锁,此时由于w1已经在线程1中上过锁了,所以必须等待;
这样两个线程都等不到对方释放锁,都处于等待状态造成了死锁。
thread提供了一个std::lock函数可以对多个互斥量同时加锁,每个线程里面的
w1和w2会同时上锁,他们之间就没有间隙了,如上将foo函数改为如下形式就可以了:
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. void foo(Widget& w1, Widget& w2)
  2. {
  3. std::unique_lock<std::mutex> t1(w1.mutex_, std::defer_lock);
  4. std::unique_lock<std::mutex> t2(w2.mutex_, std::defer_lock);
  5. std::lock(t1, t2);
  6. // do something
  7. }

在实例化的时候先不要加锁,等到两个对象都创建完成之后再一起加锁。


在初始化的时候保护数据

如果你的数据仅仅只在初始化的时候进行保护,使用一个互斥量是不行的,在初始化完成后会导致没必要的同步,C++11提供了一些方法来解决这个问题。


3.线程间同步,条件变量

如果我们在线程间共享数据,经常会存在一个线程等待另外一个线程的情况,它们之间存在先后关系。

这个与互斥量不同,互斥量是保证多个线程的时候当前只有一个线程访问加锁的代码块,它们之间是不存在先后关系的。

如下例子:线程1需要等到线程2将flag设置为非0才进行打印

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #include <iostream>
  2. #include <thread>
  3. #include <mutex>
  4. #include <condition_variable>
  5. #include <functional>
  6. class Foo
  7. {
  8. public:
  9. Foo()
  10. : flag_(0)
  11. , thread1_(std::bind(&Foo::threadFunc1, this))
  12. , thread2_(std::bind(&Foo::threadFunc2, this))
  13. {
  14. }
  15. ~Foo()
  16. {
  17. thread1_.join();
  18. thread2_.join();
  19. }
  20. private:
  21. void threadFunc1()
  22. {
  23. {
  24. std::unique_lock<std::mutex> ul(mutex_);
  25. while (0 == flag_) {
  26. cond_.wait(ul);
  27. }
  28. std::cout << flag_ << std::endl;
  29. }
  30. }
  31. void threadFunc2()
  32. {
  33. // 为了测试,等待3秒
  34. std::this_thread::sleep_for((std::chrono::milliseconds(3000)));
  35. std::unique_lock<std::mutex> ul(mutex_);
  36. flag_ = 100;
  37. cond_.notify_one();
  38. }
  39. int flag_;
  40. std::mutex mutex_;
  41. std::condition_variable cond_;
  42. std::thread thread1_;
  43. std::thread thread2_;
  44. };
  45. int _tmain(int argc, _TCHAR* argv[])
  46. {
  47. Foo f;
  48. system("pause");
  49. return 0;
  50. }

从代码可以看出,虽然线程1明显比线程2快些(人为制造等待3秒),但是线程1还是会等待线程2将flag设置为非0后才进行打印的。

这里有几个地方需要注意:

1> Foo类成员变量定义的顺序,mutex和cond必须在thread的前面。

原因是:如果线程的定义在前面,线程初始化完成之后立马会执行线程函数,而线程函数里用到了mutex和cond,此时如果mutex和cond还没初始化完成,就会出现内存错误。

2>由于同时有两个线程需要操作flag变量,所以在读写的时候要加锁, std::unique_lock<std::mutex>会保证构造的时候加锁,离开作用域调用析构的时候解锁,所以不用担心加解锁不匹配。

3>threadFunc1中的while (0 == flag_), 必须这样写不能写成if (0 == flag_),因为在多核CPU下会存在虚假唤醒( spurious wakes)的情况。

4>cond_.wait(ul);条件变量在wait的时候会释放锁的,所以其他线程可以继续执行。


4.线程池

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #include <iostream>
  2. #include <stdlib.h>
  3. #include <functional>
  4. #include <thread>
  5. #include <string>
  6. #include <mutex>
  7. #include <condition_variable>
  8. #include <vector>
  9. #include <memory>
  10. #include <assert.h>
  11. #include <algorithm>
  12. #include <queue>
  13. #include <process.h>
  14. #include <Windows.h>
  15. class ThreadPool
  16. {
  17. public:
  18. typedef std::function<void()> Task;
  19. ThreadPool(int num)
  20. : num_(num)
  21. , maxQueueSize_(0)
  22. , running_(false)
  23. {
  24. }
  25. ~ThreadPool()
  26. {
  27. if (running_) {
  28. stop();
  29. }
  30. }
  31. ThreadPool(const ThreadPool&) = delete;
  32. void operator=(const ThreadPool&) =delete;
  33. void setMaxQueueSize(int maxSize)
  34. {
  35. maxQueueSize_ = maxSize;
  36. }
  37. void start()
  38. {
  39. assert(threads_.empty());
  40. running_ = true;
  41. threads_.reserve(num_);
  42. for (int i = 0; i<num_; i++) {
  43. threads_.push_back(std::thread(std::bind(&ThreadPool::threadFunc,this)));
  44. }
  45. }
  46. void stop()
  47. {
  48. {
  49. std::unique_lock<std::mutex> ul(mutex_);
  50. running_ = false;
  51. notEmpty_.notify_all();
  52. }
  53. for (auto &iter : threads_) {
  54. iter.join();
  55. }
  56. }
  57. void run(const Task &t)
  58. {
  59. if (threads_.empty()) {
  60. t();
  61. }
  62. else {
  63. std::unique_lock<std::mutex> ul(mutex_);
  64. while (isFull()) {
  65. notFull_.wait(ul);
  66. }
  67. assert(!isFull());
  68. queue_.push_back(t);
  69. notEmpty_.notify_one();
  70. }
  71. }
  72. private:
  73. bool isFull() const
  74. {
  75. return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_;
  76. }
  77. void threadFunc()
  78. {
  79. printf("create id:%d\n", ::GetCurrentThreadId());
  80. while (running_) {
  81. Task task(take());
  82. if (task) {
  83. task();
  84. }
  85. }
  86. printf("thread id:%d\n", ::GetCurrentThreadId());
  87. }
  88. Task take()
  89. {
  90. std::unique_lock<std::mutex> ul(mutex_);
  91. while (queue_.empty() && running_) {
  92. notEmpty_.wait(ul);
  93. }
  94. Task task;
  95. if (!queue_.empty()) {
  96. task = queue_.front();
  97. queue_.pop_front();
  98. if (maxQueueSize_ > 0) {
  99. notFull_.notify_one();
  100. }
  101. }
  102. return task;
  103. }
  104. private:
  105. int num_;
  106. std::mutex mutex_;
  107. std::condition_variable notEmpty_;
  108. std::condition_variable notFull_;
  109. std::vector<std::thread> threads_;
  110. std::deque<Task> queue_;
  111. size_t maxQueueSize_;
  112. bool running_;
  113. };
  114. void fun()
  115. {
  116. printf("[id:%d] hello, world!\n", ::GetCurrentThreadId());
  117. }
  118. int _tmain(int argc, _TCHAR* argv[])
  119. {
  120. {
  121. printf("main thread id:%d\n", ::GetCurrentThreadId());
  122. ThreadPool pool(3);
  123. pool.setMaxQueueSize(100);
  124. pool.start();
  125. //std::this_thread::sleep_for(std::chrono::milliseconds(3000));
  126. for (int i = 0; i < 1000; i++) {
  127. pool.run(fun);
  128. }
  129. std::this_thread::sleep_for(std::chrono::milliseconds(3000));
  130. }
  131. system("pause");
  132. return 0;
  133. }  
0 0
原创粉丝点击