前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >c++11多线程入门教程(一)

c++11多线程入门教程(一)

作者头像
徐飞机
发布2019-05-24 08:42:59
2.2K0
发布2019-05-24 08:42:59
举报

原文作者:aircraft

原文链接:https://cloud.tencent.com/developer/article/1431240

最近是恰好写了一些c++11多线程有关的东西,就写一下笔记留着以后自己忘记回来看吧,也不是专门写给读者看的,我就想到哪就写到哪吧

  c++11呢,就是c++升级之后的一个版本,现在马上就出c++20了,里面增加了很多对多线程支持的类,让多线程编程更加简单了,好了废话不多说,先来建立一个简单的多线程编程案例,看看c++11下多线程编程创建到底有多么的简单。

1.创建一个简单的多线程案例:

首先导入#include<thread>---用于创建线程

其次导入#include<chrono>--用于时间延时 获取时间之类的

定义一个线程对象t1,这就自动创建了一个线程,参数就是你要线程去执行的函数,t1是变量名字 随便取 std::thread t1(func); 下面这里返回一个毫秒级别的时间间隔参数值,间隔10毫秒  std::chrono::milliseconds(10) this_thread::sleep_for()就是让此线程休眠,可以传入休眠的时间 this_thread::sleep_for(std::chrono::milliseconds(10));让本线程休眠10毫秒

好了知道这些参数意思就行了,看一下代码:

代码语言:javascript
复制
#include<windows.h>
#include <iostream>
#include <chrono>
#include <thread>
using namespace std;

int number = 1;

int ThreadProc1()
{
    while (number < 100)
    {
        cout << "thread 1 :" << number << endl;
        ++number;
       this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    return 0;
}

int ThreadProc2()
{
    while (number < 100)
    {
        cout << "thread 2 :" << number << endl;
        ++number;
        this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    return 0;
}

int main()
{
    thread t1(ThreadProc1);
    thread t2(ThreadProc2);

    t1.join();
    t2.join();

    system("pause");
    return 0;
}

 join()就是阻塞线程,直到线程函数执行完毕,如果函数有返回值,在这里会直接忽略。阻塞的目的就是让Main主线程等待一下创建的线程,免得我函数还在跑,程序就直接结束了。

  如果不想阻塞在这里就将join()换成使用线程的detach()方法,将线程与线程对象分离,线程就可以继续运行下去,并且不会造成影响。

  从示例可以看到c++11下创建多线程多么方便了吧 ,比在Linux下用posix创建还简便,而这个也是可以在windows使用的(想想windows下多线程的代码,看着都头疼好吧,乱七八糟一大堆)。

2.互斥量的使用

  跟往常的多线程一样,多线程在运行过程中都会对临界区进行访问,也就是一起访问共享资源。这样就会造成一个问题,当两个线程都要对一个变量int value值假如为11,加一时,线程一取出11 进行加一还没有存入value,这时候线程二又取得value的11进行加一,然后线程一存入12,线程二又存入12,这就导入两个线程访问冲突,也就是临界区问题。所以引进互斥量来解决。

导入#include <mutex>

代码案例:

一个线程对变量number进行加一100次,另外一个减一100次,最后结果应该还是原来的值0。

代码语言:javascript
复制
#include<windows.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;

int number = 0;
mutex g_lock;

int ThreadProc1()
{
    
    for (int i = 0; i < 100; i++)
    {
        g_lock.lock();
        ++number;
        cout << "thread 1 :" << number << endl;
        g_lock.unlock();
        this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    return 0;
}

int ThreadProc2()
{
    
    for (int i = 0; i < 100; i++)
    {
        g_lock.lock();
        --number;
        cout << "thread 2 :" << number << endl;
        g_lock.unlock();
        this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    return 0;
}

int main()
{
    thread t1(ThreadProc1);
    thread t2(ThreadProc2);

    t1.detach();
    t2.detach();

    system("pause");
    return 0;
}

上面的每次都要对mutex变量进行锁以及解锁,有时候忘记解锁就凉凉了。所以c++11还提供了一个lock_guard类,它利用了RAII机制可以保证安全释放mutex。

在std::lock_guard对象构造时,传入的mutex对象(即它所管理的mutex对象)会被当前线程锁住。在lock_guard对象被析构时,它所管理的mutex对象会自动解锁,不需要程序员手动调用lock和unlock对mutex进行上锁和解锁操作。lock_guard对象并不负责管理mutex对象的生命周期,lock_guard对象只是简化了mutex对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个lock_guard对象的生命周期内,它所管理的锁对象会一直保持上锁状态;而lock_guard的生命周期结束之后,它所管理的锁对象会被解锁。程序员可以非常方便地使用lock_guard,而不用担心异常安全问题。

代码:

代码语言:javascript
复制
#include<windows.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;

int number = 0;
mutex g_lock;

int ThreadProc1()
{
    lock_guard<mutex> loker(mutex);
    for (int i = 0; i < 100; i++)
    {
        ++number;
        cout << "thread 1 :" << number << endl;
        
    }
    //this_thread::sleep_for(std::chrono::milliseconds(100));
    return 0;
}

int ThreadProc2()
{
    lock_guard<mutex> loker(mutex);
    for (int i = 0; i < 100; i++)
    {
        --number;
        cout << "thread 2 :" << number << endl;
        //this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    return 0;
}

int main()
{
    thread t1(ThreadProc1);
    thread t2(ThreadProc2);

    t1.detach();
    t2.detach();

    system("pause");
    return 0;
}

除了lock_guard,之外c++11还提供了std::unique_lock

类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。 unique_lock比lock_guard使用更加灵活,功能更加强大。 使用unique_lock需要付出更多的时间、性能成本。

代码语言:javascript
复制
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock
#include <vector>

std::mutex mtx;           // mutex for critical section
std::once_flag flag;        //定义一个once_flag类型的变量作为call_once参数,    
                                    //用std::call_once来保证多线程环境中只被调用一次
void print_block (int n, char c) {
    //unique_lock有多组构造函数, 这里std::defer_lock不设置锁状态
    std::unique_lock<std::mutex> my_lock (mtx, std::defer_lock);
    //尝试加锁, 如果加锁成功则执行
    //(适合定时执行一个job的场景, 一个线程执行就可以, 可以用更新时间戳辅助)
    if(my_lock.try_lock()){
        for (int i=0; i<n; ++i)
            std::cout << c;
        std::cout << '\n';
    }
}

void run_one(int &n){
    std::call_once(flag, [&n]{n=n+1;}); //只执行一次, 适合延迟加载; 多线程static变量情况
}

int main () {
    std::vector<std::thread> ver;
    int num = 0;
    for (auto i = 0; i < 10; ++i){
        ver.emplace_back(print_block,50,'*');  
        ver.emplace_back(run_one, std::ref(num));
  //emplace_back比push_back更好 是c++11增加的
    }

    for (auto &t : ver){
        t.join();
    }
    std::cout << num << std::endl;
    return 0;
}                    

3.原子变量的使用

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

  上面我们用互斥锁来实现加一百次,减少一百次。使用原子变量会更加简洁。

代码语言:javascript
复制
#include<windows.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;

atomic<int> number(0);//定义原子变量 一次只允许一个线程对其进行访问
//int number = 0;
//mutex g_lock;

int ThreadProc1()
{
    //lock_guard<mutex> loker(mutex);
    for (int i = 0; i < 100; i++)
    {
        ++number;
        cout << "thread 1 :" << number << endl;
        
    }
    //this_thread::sleep_for(std::chrono::milliseconds(100));
    return 0;
}

int ThreadProc2()
{
    //lock_guard<mutex> loker(mutex);
    for (int i = 0; i < 100; i++)
    {
        --number;
        cout << "thread 2 :" << number << endl;
        //this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    return 0;
}

int main()
{
    thread t1(ThreadProc1);
    thread t2(ThreadProc2);

    t1.detach();
    t2.detach();

    system("pause");
    return 0;
}

可以看到使用了原子变量之后,代码简化了很多,以及以后对某些共享资源我们都可以酌情的定义为原子变量类型,很方便有木有。。。。。

  本来想写完几个c++11多线程常用的函数和操作的,但是有点想去看一下哥斯拉2,但是哥斯拉1还没看,先去看哥斯拉1好了  今天就先写这么多了hhhhhh

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-05-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档