C++11多线程编程基础入门

c++bce3b83f770dfdf50c5dae0e4360a>1.在C++11中创建新线程

  在每个c++应用程序中,都有一个默认的主线程,即main函数,在c++11中,我们可以通过创建std::thread类的对象来创建其他线程,每个std :: thread对象都可以与一个线程相关联,只需包含头文件。可以使用std :: thread对象附加一个回调,当这个新线程启动时,它将被执行。 这些回调可以为函数指针、函数对象、Lambda函数。
  线程对象可通过std::thread thObj()来创建,新线程将在创建新对象后立即开始,并且将与已启动的线程并行执行传递的回调。此外,任何线程可以通过在该线程的对象上调用join()函数来等待另一个线程退出。
  使用函数指针创建线程:

//main.cpp#include #include void thread_function() {        for (int i = 0; i < 5; i++)            std::cout << "thread function excuting" << std::endl;}int main() {        std::thread threadObj(thread_function);        for (int i = 0; i < 5; i++)            std::cout << "Display from MainThread" << std::endl;   threadObj.join();       std::cout << "Exit of Main function" << std::endl;    return 0;}

  使用函数对象创建线程:

#include #include class DisplayThread {    public:void operator ()() {                for (int i = 0; i < 100; i++)                    std::cout << "Display Thread Excecuting" << std::endl;    }};int main() {        std::thread threadObj((DisplayThread()));        for (int i = 0; i < 100; i++)            std::cout << "Display From Main Thread " << std::endl;        std::cout << "Waiting For Thread to complete" << std::endl;    threadObj.join();        std::cout << "Exiting from Main Thread" << std::endl;        return 0;}

CmakeLists.txt

cmake_minimum_required(VERSION 3.10)project(Thread_test)set(CMAKE_CXX_STANDARD 11)find_package(Threads REQUIRED)add_executable(Thread_test main.cpp)target_link_libraries(Thread_test ${CMAKE_THREAD_LIBS_INIT})

每个std::thread对象都有一个相关联的id,std::thread::get_id() —-成员函数中给出对应线程对象的id;
std::this_thread::get_id()—-给出当前线程的id,如果std::thread对象没有关联的线程,get_id()将返回默认构造的std::thread::id对象:“not any thread”,std::thread::id也可以表示id。

2.joining和detaching 线程

线程一旦启动,另一个线程可以通过调用std::thread对象上调用join()函数等待这个线程执行完毕:

std::thread threadObj(funcPtr); threadObj.join();

例如,主线程启动10个线程,启动完毕后,main函数等待他们执行完毕,join完所有线程后,main函数继续执行:

立即学习“C++免费学习笔记(深入)”;

#include #include #include class WorkerThread{    public:void operator()(){                std::cout<<"Worker Thread "<<std::this_thread::get_id()<<"is Excecuting"<<std::endl;    }};    int main(){            std::vector threadList;            for(int i = 0; i < 10; i++){        threadList.push_back(std::thread(WorkerThread()));    }        // Now wait for all the worker thread to finish i.e.    // Call join() function on each of the std::thread object    std::cout<<"Wait for all the worker thread to finish"<<std::endl;        std::for_each(threadList.begin(), threadList.end(), std::mem_fn(&std::thread::join));        std::cout<<"Exiting from Main Thread"<<std::endl;        return 0;}

detach可以将线程与线程对象分离,让线程作为后台线程执行,当前线程也不会阻塞了.但是detach之后就无法在和线程发生联系了.如果线程执行函数使用了临时变量可能会出现问题,线程调用了detach在后台运行,临时变量可能已经销毁,那么线程会访问已经被销毁的变量,需要在std::thread对象中调用std::detach()函数:

std::thread threadObj(funcPtr)threadObj.detach();

调用detach()后,std::thread对象不再与实际执行线程相关联,在线程句柄上调用detach() 和 join()要小心.

3.将参数传递给线程

要将参数传递给线程的可关联对象或函数,只需将参数传递给std::thread构造函数,默认情况下,所有的参数都将复制到新线程的内部存储中。
给线程传递参数:

#include #include #include void threadCallback(int x, std::string str) {      std::cout << "Passed Number = " << x << std::endl;      std::cout << "Passed String = " << str << std::endl;}int main() {      int x = 10;      std::string str = "Sample String";      std::thread threadObj(threadCallback, x, str);  threadObj.join();    return 0;}

  给线程传递引用:

#include #include void threadCallback(int const& x) {      int& y = const_cast(x);  y++;    std::cout << "Inside Thread x = " << x << std::endl;}int main() {      int x = 9;      std::cout << "In Main Thread : Before Thread Start x = " << x << std::endl;      std::thread threadObj(threadCallback, x);  threadObj.join();    std::cout << "In Main Thread : After Thread Joins x = " << x << std::endl;    return 0;}

输出结果为:
In Main Thread : Before Thread Start x = 9
Inside Thread x = 10
In Main Thread : After Thread Joins x = 9

Process finished with exit code 0
即使threadCallback接受参数作为引用,但是并没有改变main中x的值,在线程引用外它是不可见的。这是因为线程函数threadCallback中的x是引用复制在新线程的堆栈中的临时值,使用std::ref可进行修改:

#include #include void threadCallback(int const& x) {      int& y = const_cast(x);  y++;    std::cout << "Inside Thread x = " << x << std::endl;}int main() {      int x = 9;  std::cout << "In Main Thread : Before Thread Start x = " << x << std::endl;      std::thread threadObj(threadCallback, std::ref(x));  threadObj.join();    std::cout << "In Main Thread : After Thread Joins x = " << x << std::endl;    return 0;}

输出结果为:
In Main Thread : Before Thread Start x = 9
Inside Thread x = 10
In Main Thread : After Thread Joins x = 10

Process finished with exit code 0
指定一个类的成员函数的指针作为线程函数,将指针传递给成员函数作为回调函数,并将指针指向对象作为第二个参数:

#include #include class DummyClass { public:  DummyClass() { }  DummyClass(const DummyClass& obj) { }    void sampleMemberfunction(int x) {          std::cout << "Inside sampleMemberfunction " << x << std::endl;  }};    int main() {      DummyClass dummyObj;        int x = 10;        std::thread threadObj(&DummyClass::sampleMemberfunction, &dummyObj, x);      threadObj.join();        return 0;}

4.线程间数据的共享与竞争条件

在多线程间的数据共享很简单,但是在程序中的这种数据共享可能会引起问题,其中一种便是竞争条件。当两个或多个线程并行执行一组操作,访问相同的内存位置,此时,它们中的一个或多个线程会修改内存位置中的数据,这可能会导致一些意外的结果,这就是竞争条件。竞争条件通常较难发现并重现,因为它们并不总是出现,只有当两个或多个线程执行操作的相对顺序导致意外结果时,它们才会发生。
例如创建5个线程,这些线程共享类Wallet的一个对象,使用addMoney()成员函数并行添加100元。所以,如果最初钱包中的钱是0,那么在所有线程的竞争执行完毕后,钱包中的钱应该是500,但是,由于所有线程同时修改共享数据,在某些情况下,钱包中的钱可能远小于500。
测试如下:

#include #include #include class Wallet {        int mMoney;    public: Wallet() : mMoney(0) { }        int getMoney() { return mMoney; }        void addMoney(int money) {                for (int i = 0; i < money; i++) {            mMoney++;        }    }};int testMultithreadWallet() {    Wallet walletObject;        std::vector threads;        for (int i = 0; i < 5; i++) {        threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100));    }    for (int i = 0; i < 5; i++) {        threads.at(i).join();    }        return walletObject.getMoney();}int main() {            int val = 0;            for (int k = 0; k < 100; k++) {                if ((val=testMultithreadWallet()) != 500) {                        std::cout << "Error at count = " << k << " Money in Wallet = " << val << std::endl;        }    }        return 0;}

每个线程并行地增加相同的成员变量“mMoney”,看似是一条线,但是这个“nMoney++”实际上被转换为3条机器命令:
·在Register中加载”mMoney”变量
·增加register的值
·用register的值更新“mMoney”变量
在这种情况下,一个增量将被忽略,因为不是增加mMoney变量,而是增加不同的寄存器,“mMoney”变量的值被覆盖。

5.使用mutex处理竞争条件

为了处理多线程环境中的竞争条件,我们需要mutex互斥锁,在修改或读取共享数据前,需要对数据加锁,修改完成后,对数据进行解锁。在c++11的线程库中,mutexes在头文件中,表示互斥体的类是std::mutex。
就上面的问题进行处理,Wallet类提供了在Wallet中增加money的方法,并且在不同的线程中使用相同的Wallet对象,所以我们需要对Wallet的addMoney()方法加锁。在增加Wallet中的money前加锁,并且在离开该函数前解锁,看代码:Wallet类内部维护money,并提供函数addMoney(),这个成员函数首先获取一个锁,然后给wallet对象的money增加指定的数额,最后释放锁。

#include #include #include #include class Wallet {            int mMoney;            std::mutex mutex;public:    Wallet() : mMoney(0) { }        int getMoney() { return mMoney;}        void addMoney(int money) {        mutex.lock();                for (int i = 0; i < money; i++) {            mMoney++;        }        mutex.unlock();    }};int testMultithreadWallet() {    Wallet walletObject;        std::vector threads;        for (int i = 0; i < 5; ++i) {        threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 1000));    }    for (int i = 0; i < threads.size(); i++) {        threads.at(i).join();    }        return walletObject.getMoney();}int main() {            int val = 0;            for (int k = 0; k < 1000; k++) {                if ((val = testMultithreadWallet()) != 5000) {                    std::cout << "Error at count= " << k << " money in wallet" << val << std::endl;        }    }        return 0;}

这种情况保证了钱包里的钱不会出现少于5000的情况,因为addMoney()中的互斥锁确保了只有在一个线程修改完成money后,另一个线程才能对其进行修改,但是,如果我们忘记在函数结束后对锁进行释放会怎么样?这种情况下,一个线程将退出而不释放锁,其他线程将保持等待,为了避免这种情况,我们应当使用std::lock_guard,这是一个template class,它为mutex实现RALL,它将mutex包裹在其对象内,并将附加的mutex锁定在其构造函数中,当其析构函数被调用时,它将释放互斥体。

class Wallet {      int mMoney;      std::mutex mutex; public:  Wallet() : mMoney(0) { }  int getMoney() { return mMoney;}    void addMoney(int money) {      std::lock_guard lockGuard(mutex);      for (int i = 0; i < mMoney; ++i) {        //如果在此处发生异常,lockGuadr的析构函数将会因为堆栈展开而被调用      mMoney++;            //一旦函数退出,那么lockGuard对象的析构函数将被调用,在析构函数中mutex会被释放    }  }};

6.条件变量

  条件变量是一种用于在2个线程之间进行信令的事件,一个线程可以等待它得到信号,其他的线程可以给它发信号。在c++11中,条件变量需要头文件,同时,条件变量还需要一个mutex锁。
  条件变量是如何运行的:
  ·线程1调用等待条件变量,内部获取mutex互斥锁并检查是否满足条件;
  ·如果没有,则释放锁,并等待条件变量得到发出的信号(线程被阻塞),条件变量的wait()函数以原子方式提供这两个操作;
  ·另一个线程,如线程2,当满足条件时,向条件变量发信号;
  ·一旦线程1正等待其恢复的条件变量发出信号,线程1便获取互斥锁,并检查与条件变量相关关联的条件是否满足,或者是否是一个上级调用,如果多个线程正在等待,那么notify_one将只解锁一个线程;
  ·如果是一个上级调用,那么它再次调用wait()函数。
  条件变量的主要成员函数:
Wait()
它使得当前线程阻塞,直到条件变量得到信号或发生虚假唤醒;
它原子性地释放附加的mutex,阻塞当前线程,并将其添加到等待当前条件变量对象的线程列表中,当某线程在同样的条件变量上调用notify_one() 或者 notify_all(),线程将被解除阻塞;
这种行为也可能是虚假的,因此,解除阻塞后,需要再次检查条件;
一个回调函数会传给该函数,调用它来检查其是否是虚假调用,还是确实满足了真实条件;
当线程解除阻塞后,wait()函数获取mutex锁,并检查条件是否满足,如果条件不满足,则再次原子性地释放附加的mutex,阻塞当前线程,并将其添加到等待当前条件变量对象的线程列表中。
notify_one()
如果所有线程都在等待相同的条件变量对象,那么notify_one会取消阻塞其中一个等待线程。
notify_all()
如果所有线程都在等待相同的条件变量对象,那么notify_all会取消阻塞所有的等待线程。

#include #include #include #include #include using namespace std::placeholders;class Application {        std::mutex m_mutex;        std::condition_variable m_condVar;        bool m_bDataLoaded;public:  Application() {        m_bDataLoaded = false;    }        void loadData() {                    //使该线程sleep 1秒        std::this_thread::sleep_for(std::chrono::milliseconds(1000));                std::cout << "Loading Data from XML" << std::endl;        //锁定数据        std::lock_guard guard(m_mutex);        //flag设为true,表明数据已加载        m_bDataLoaded = true;        //通知条件变量        m_condVar.notify_one();    }    bool isDataLoaded() {                     return m_bDataLoaded;    }    void mainTask() {                    std::cout << "Do some handshaking" << std::endl;        //获取锁        std::unique_lock mlock(m_mutex);        //开始等待条件变量得到信号        //wait()将在内部释放锁,并使线程阻塞        //一旦条件变量发出信号,则恢复线程并再次获取锁        //然后检测条件是否满足,如果条件满足,则继续,否则再次进入wait        m_condVar.wait(mlock, std::bind(&Application::isDataLoaded, this));                std::cout << "Do Processing On loaded Data" << std::endl;    }};int main() {    Application app;        std::thread thread_1(&Application::mainTask, &app);        std::thread thread_2(&Application::loadData, &app);    thread_2.join();    thread_1.join();    return 0;}

以上就是C++11多线程编程基础入门的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1432753.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月17日 08:34:25
下一篇 2025年12月13日 03:28:00

相关推荐

  • 关于C#中字典Dictionary的顺序及倒序详解

    C# .net 3.5 以上的版本引入 Linq 后,字典Dictionary排序变得十分简单,用一句类似 sql 数据库查询语句即可搞定;不过,.net 2.0 排序要稍微麻烦一点,为便于使用,将总结 .net 3.5 和 2.0 的排序方法。 一、创建字典Dictionary 对象   假如 D…

    好文分享 2025年12月17日
    000
  • C#中关于概念系统的总结

    font-size:14px”>1.什么是.net.net是由microsoft推出的应用程序开发平台,可用来构建和运行新一代microsoft windows和web应用程序。 2. .net的核心技术.net framework:.net 平台核心中的核心,为.net 平台下的…

    好文分享 2025年12月17日
    000
  • C#开发者必须知道的13件事情

    c#开发者必须知道的13件事情 1.开发流程 程序的Bug与瑕疵往往出现于开发流程当中。只要对工具善加利用,就有助于在你发布程序之前便将问题发现,或避开这些问题。 标准化代码书写 标准化代码书写可以使代码更加易于维护,尤其是在代码由多个开发者或团队进行开发与维护时,这一优点更加突出。常见的强制代码规…

    2025年12月17日
    000
  • C#实现导入导出Excel数据的两种方法详解

    这篇文章主要为大家详细介绍了c#导入导出excel数据的两种方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 本文为大家分享了C#导入导出Excel数据的具体代码,供大家参考,具体内容如下 注:对于实体类对象最好新建一个并且继承原有实体类,这样可以将类型进行修改; 方法一:此种方法是用EPPL…

    好文分享 2025年12月17日
    000
  • c#DevExpress gridcontrol日期行的显示格式设置详解(图文)

    这篇文章主要介绍了c# devexpress gridcontrol日期行的显示格式设置,需要的朋友可以参考下 如上图所示,日期显示为”MM月DD日”,或者其它格式,比如显示年、月、日 或 年月日带时间,设置如下: 1、如下图设置,日期为d,时间为t: 2、在事件里面如下写法…

    2025年12月17日 好文分享
    000
  • c# GridControl的模糊查询实现代码实例

    这篇文章主要介绍了c# gridcontrol的模糊查询实现代码,需要的朋友可以参考下 如上图所示,如果查询供应商名称包括机械的公司,正常设置是不可以的,只能从头开始筛选: 方法1: 以下是以为网名为[不是小宽]的网友发给我的完美解决方案,我在此贴出来,大家可以共同学习: /// /// 设置gir…

    2025年12月17日
    000
  • 详细介绍C# string格式的日期时间字符串转为DateTime类型的方法

    这篇文章主要介绍了c# string格式的日期时间字符串转为datetime类型的方法,需要的朋友可以参考下 方法一:Convert.ToDateTime(string) string格式有要求,必须是yyyy-MM-dd hh:mm:ss 方法二:Convert.ToDateTime(string…

    好文分享 2025年12月17日
    000
  • 详解c#Winform程序自动更新实现方法(图)

    winform程序自动更新我也是第一次做,网上找了自动更新的源码,后来又根据在网上看到的一些方法,自己试了很久,最终还是有写错误,所以花了钱让别人帮忙调试成功的,下面是我自己捣腾出来的,方便大家借鉴,如果有什么错误的地方欢迎指正 Winform程序自动更新我也是第一次做,网上找了自动更新的源码,后来…

    2025年12月17日 好文分享
    000
  • C#泛型类型的详细介绍

    这篇文章主要介绍c#泛型类型,非常不错,具有参考借鉴价值,需要的朋友可以参考下 上篇文章给大家介绍了浅析C# 中的类型系统(值类型和引用类型),接下来通过本文给大家介绍下c# 泛型类型, 说下C#中的泛型,熟练地使用泛型能提高代码的重用性,使用我们代码瞬间就高大上了,当然只有一点点,真的只有一点点,…

    2025年12月17日
    000
  • C#中的类型系统(值类型和引用类型)的简单介绍

    这篇文章主要介绍了浅析c# 中的类型系统(值类型和引用类型),需要的朋友可以参考下 今天要写的东西都是书中一些概念性的东西,就当抄笔记,以提问对话的方式将其写出来吧,说不定以后面试能有点谈资~~~   Q1.C#1系统类型包含哪三点特性?   A1.C#1类型系统是静态的、显式的和安全的。   Q2…

    好文分享 2025年12月17日
    000
  • 实现C#中图片.BYTE[]和base64string的转换方法的详解

    下面小编就为大家带来一篇c#中图片.byte[]和base64string的转换方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 在C#中      图片到byte[]再到base64string的转换: Bitmap bmp = new Bitmap(filepa…

    好文分享 2025年12月17日
    000
  • 详解C#接口在派生类和外部类中的调用方法示例

    这篇文章主要介绍了c#接口在派生类和外部类中的调用方法,结合实例形式分析了c#接口的定义与具体使用方法,需要的朋友可以参考下 本文实例讲述了C#接口在派生类和外部类中的调用方法。分享给大家供大家参考,具体如下: C#的接口通过interface关键字进行创建,在接口中可以包含属性,方法等成员变量。接…

    好文分享 2025年12月17日
    000
  • 详细介绍C#代码与javaScript函数的相互调用

    C#代码与JavaScript函数的相互调用 问:1.如何在javascript访问c#函数?2.如何在javascript访问c#变量?3.如何在c#中访问javascript的已有变量?4.如何在c#中访问javascript函数? 问题1答案如下:javascript函数中执行c#代码中的函数…

    好文分享 2025年12月17日
    000
  • C#解析XML文件的代码实例分享

    c#解析xml文件的代码实例分享 XmlNodeReader reader = null; try { XmlDocument xd = new XmlDocument(); xd.Load(filename); reader = new XmlNodeReader(xd); //创建新的XML r…

    好文分享 2025年12月17日
    000
  • c#ref关键字的示例代码分享

    c# 语言参考 ref(C# 参考) ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。例如:  复制代码 class RefExample{ static …

    好文分享 2025年12月17日
    000
  • 具体介绍C#线程与线程池的区别

    线程的建立:(不同于java的是不用再继承thread类) TcpClient tc = tListener.AcceptTcpClient(); CThreadServer ctserver = new CThreadServer(tc); Thread t = new Thread(new Th…

    好文分享 2025年12月17日
    000
  • 详情介绍c#中Winform实现多线程异步更新UI的示例代码

    本篇文章主要介绍了c#中winform实现多线程异步更新ui(进度及状态信息) ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 引言 在进行Winform程序开发需要进行大量的数据的读写操作的时候,往往会需要一定的时间,然在这个时间段里面,界面ui得不到更新,导致在用户…

    2025年12月17日
    000
  • C#灵活使用类的方法分享

    本文主要介绍了c# 灵活使用类的方法,具有很好的参考价值,下面跟着小编一起来看下吧 构造函数 概括:构造函数是类中的一种特殊的方法,主要完成对象的初始化工作,在创建对象的时候完成指定的工作。而且构造函数方法名和类名相同,没有返回值类型。 无参构造函数 在默认的情况下,系统会给类分配一个无参构造函数,…

    好文分享 2025年12月17日
    000
  • C# IEnumerable和IEnumerator接口的简单介绍

    本文主要介绍了c#中ienumerable和ienumerator接口的相关知识,具有很好的参考价值,下面跟着小编一起来看下吧 温故而知新,可以为师矣,有空经常复习一下基础知识是有必要的,并且能加深理解和记忆。 Foreach常用于循环访问集合,对实现IEnumerable的接口的容器进行遍历,IE…

    2025年12月17日
    000
  • C#实现Access通用访问类OleDbHelper的示例代码

    这篇文章主要介绍了c#实现access通用访问类oledbhelper,结合完整实例形式分析了c#针对access数据库的连接、查询、遍历、分页显示等相关操作技巧,需要的朋友可以参考下 本文实例讲述了C#实现Access通用访问类OleDbHelper。分享给大家供大家参考,具体如下: 最近在做一个…

    好文分享 2025年12月17日
    000

发表回复

登录后才能评论
关注微信