C++11:多线程与锁

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

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
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
 
volatile int val;
mutex mut;
 
void icrement () {
    for (int i = 0; i < 100000000; i++) {
        mut.lock ();
        val++;
        mut.unlock ();
    }
}
 
int main (int argc, char* argv []) {
    //创建两个线程
    thread t1 (icrement);
    thread t2 (icrement);
    //等待两个线程执行完
    t1.join ();
    t2.join ();
    cout << val << endl;
    return 0;
}

概念有点多。首先得引入thread与mutex这两个头文件,其中thread与线程有关,mutex与锁有关。然后是两个全局变量,由于两个线程都会访问,所以加上volatile关键字,代表不要对这变量进行优化,然后是mutex,这个就是锁咯。
然后,下面的main函数中,首先创建两个线程,然后等待两个线程执行完,然后输出结果。最后是increment函数,这函数循环一亿次,不停的加锁解锁,控制着val变量的访问。锁还提供一个函数,比如这样调用 mut.try_lock (); ,立即返回bool类型,true代表加锁成功,false代表加锁失败。

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

1
2
3
4
5
6
7
void icrement () {
    for (int i = 0; i < 100000000; i++) {
        //调用即加锁,离开大括号作用域自动解锁
        lock_guard<mutex> lock (mut);
        val++;
    }
}

自动锁就不用刻意的解锁了,对于多分支的情况相当方便。
除了普通的锁之外,还有三种锁:

1
2
3
recursive_mutex rm;//递归锁
recursive_timed_mutex rtm;//递归超时锁
timed_mutex tm;//超时锁

递归锁用在递归的场合,超时锁也就是普通锁加上超时功能,基本功能与锁相同。
锁方便控制代码逻辑,但如果只是控制访问一个变量的话,有一种更好的选择,那就是原子。详见C++11:原子操作
关于代码逻辑的控制,除了thread之外,还有一种,std::async,用于接收线程函数的返回值。关于返回值,通过thread的引用参数也可以传递,std::async只是让代码逻辑更清晰而已。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>
#include <future>
 
int main (int argc, char* argv []) {
    std::future<int> future = std::async (std::launch::async, [] () {
        std::this_thread::sleep_for (std::chrono::seconds (3));
        return 4;
    });
    std::future_status status;
    do {
        status = future.wait_for (std::chrono::seconds (1));
        if (status == std::future_status::ready) {
            std::cout << "std::future_status::ready" << std::endl;
        } else if (status == std::future_status::timeout) {
            std::cout << "std::future_status::timeout" << std::endl;
        } else if (status == std::future_status::deferred) {
            std::cout << "std::future_status::deferred" << std::endl;
        }
    } while (status != std::future_status::ready);
    std::cout << future.get () << std::endl;
    return 0;
}

使用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,用来等待线程执行结束,效果差不多。
以上代码不出所料,执行结果如下:
20151109200411596

发布者

fawdlstty

又一只萌萌哒程序猿~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注