C++:多线程与锁

多线程是小型软件开发必然的趋势。C++11将多线程相关操作全部集成到标准库中了,省去了某些坑库的编译,真是大大的方便了软件开发。多线程这个库简单方便实用,下面给出简单的例子

 
  1. #include <iostream>

  2. #include <thread>

  3. #include <mutex>

  4. using namespace std;

  5.  
  6. volatile int val;

  7. mutex mut;

  8.  
  9. void icrement () {

  10. for (int i = 0; i < 100000000; i++) {

  11. mut.lock ();

  12. val++;

  13. mut.unlock ();

  14. }

  15. }

  16.  
  17. int main (int argc, char* argv []) {

  18. //创建两个线程

  19. thread t1 (icrement);

  20. thread t2 (icrement);

  21. //等待两个线程执行完

  22. t1.join ();

  23. t2.join ();

  24. cout << val << endl;

  25. return 0;

  26. }

概念有点多。首先得引入thread与mutex这两个头文件,其中thread与线程有关,mutex与锁有关。然后是两个全局变量,由于两个线程都会访问,所以加上volatile关键字,代表不要对这变量进行优化,然后是mutex,这个就是锁咯。

然后,下面的main函数中,首先创建两个线程,然后等待两个线程执行完,然后输出结果。最后是increment函数,这函数循环一亿次,不停的加锁解锁,控制着val变量的访问。锁还提供一个函数,比如这样调用 mut.try_lock (); ,立即返回bool类型,true代表加锁成功,false代表加锁失败。

这是最基本的情况。对于普通的加锁解锁操作,使用lock、unlock就够用了。但如果分支太多,这个就不好控制了。C++11提供了一种自动锁的机制,比如上面代码可以替换成这样:

 
  1. void icrement () {

  2. for (int i = 0; i < 100000000; i++) {

  3. //调用即加锁,离开大括号作用域自动解锁

  4. lock_guard<mutex> lock (mut);

  5. val++;

  6. }

  7. }

自动锁就不用刻意的解锁了,对于多分支的情况相当方便。

除了普通的锁之外,还有三种锁:

 
  1. recursive_mutex rm;//递归锁

  2. recursive_timed_mutex rtm;//递归超时锁

  3. timed_mutex tm;//超时锁

递归锁用在递归的场合,超时锁也就是普通锁加上超时功能,基本功能与锁相同。

锁方便控制代码逻辑,但如果只是控制访问一个变量的话,有一种更好的选择,那就是原子。详见C++11:原子操作

关于代码逻辑的控制,除了thread之外,还有一种,std::async,用于接收线程函数的返回值。关于返回值,通过thread的引用参数也可以传递,std::async只是让代码逻辑更清晰而已。示例代码如下:

 
  1. #include <iostream>

  2. #include <string>

  3. #include <future>

  4.  
  5. int main (int argc, char* argv []) {

  6. std::future<int> future = std::async (std::launch::async, [] () {

  7. std::this_thread::sleep_for (std::chrono::seconds (3));

  8. return 4;

  9. });

  10. std::future_status status;

  11. do {

  12. status = future.wait_for (std::chrono::seconds (1));

  13. if (status == std::future_status::ready) {

  14. std::cout << "std::future_status::ready" << std::endl;

  15. } else if (status == std::future_status::timeout) {

  16. std::cout << "std::future_status::timeout" << std::endl;

  17. } else if (status == std::future_status::deferred) {

  18. std::cout << "std::future_status::deferred" << std::endl;

  19. }

  20. } while (status != std::future_status::ready);

  21. std::cout << future.get () << std::endl;

  22. return 0;

  23. }

使用async首先需要引入future这个头文件。然后是std::async这个函数,它返回一个std::future这个模板类型,类型取决于函数的返回值。

第一个参数代表启动方式,std::launch::async代表调用函数时启动,std::launch::deferred代表创建后挂起线程,当执行std::future<>.get()时才执行线程。

第二个参数传入函数,可以是函数地址、lambda表达式或仿函数等。如果有参数的话,可以在调用std::async时加参数就行了。

这个线程执行两句话,sleep_for这行的意思是线程暂停3秒。个人感觉这语法太复杂了,还不如微软Sleep来的简洁。暂停线程之后,直接返回4。

然后 我们定义一个future的状态,future_status,用于获取线程执行结果。这儿可以直接用get(),表示等待线程执行完,那么就不用future了,也跟单线程没区别了,所以还是用wait_for来获取状态。获取状态后进行判断。如果为std::future_status::ready,代表线程已经执行完毕;如果为std::future_status::timeout,表示等待了wait_for传入的时间之后,线程还没执行完毕,还在继续执行;如果为std::future_status::deferred,代表线程还处于挂起状态。我用的VS2013 Communicaty Update4,发现无论如何也无法处于std::future_status::deferred状态,据说是我这个版本的bug,在VS2015中修复。个人建议,挂起线程这个尽量不用,调用get来获取还不如直接单线程来的直接。

然后是future.get,用于获取线程的返回值。如果线程并没执行完那么就一直等着。除此之外还有一个相似的函数,wait,用来等待线程执行结束,效果差不多。

以上代码不出所料,执行结果如下:

猜你喜欢

转载自blog.csdn.net/xionglifei2014/article/details/82423292