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)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
技术解答面向对象的初步认识(C++ 类)
上一篇 2025年12月17日 08:34:25
下一篇 2025年12月17日 08:34:45

相关推荐

  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • c#文件怎么打开

    打开 C# 文件有三种方法:Visual Studio:启动 Visual Studio,通过“文件”菜单打开 C# 文件。文本编辑器:使用文本编辑器打开 C# 文件,将其视为普通文本。.NET Core 命令行工具:使用 csc.exe 命令行工具编译 C# 文件,生成可执行文件。 如何打开 C#…

    2026年5月10日
    000
  • c++如何实现UDP通信_c++基于UDP的网络通信示例

    UDP通信基于套接字实现,适用于实时性要求高的场景。1. 流程包括创建套接字、绑定地址(接收方)、发送(sendto)与接收(recvfrom)数据、关闭套接字;2. 服务端监听指定端口,接收客户端消息并回传;3. 客户端发送消息至服务端并接收响应;4. 跨平台需处理Winsock初始化与库链接,编…

    2026年5月10日
    100
  • 函数指针在 C++ 多态中的作用:揭示多态背后的真相

    函数指针在 C++ 多态中的作用:揭示多态背后的真相 简介 多态是面向对象编程的一项强大功能,它允许对象在运行时以不同的方式表现。C++ 中的多态实现依赖于函数指针。本文将深入探讨函数指针在多态中的作用,并通过一个实战案例展示如何利用它们。 函数指针 立即学习“C++免费学习笔记(深入)”; 函数指…

    2026年5月10日
    000
  • C++框架与Java框架在易用性方面的比较

    c++++ 框架的易用性低于 java 框架,具体原因如下:c++ 框架学习曲线陡峭,需要深入理解 c++ 语言。易出错且调试困难。而 java 框架具有以下易用性优势:学习曲线低,尤其适合 java 初学者。提供丰富的库和工具,简化开发。运行时异常处理,简化异常处理。 C++ 框架与 Java 框…

    2026年5月10日
    000
  • c++中头文件和源文件的区别_c++头文件与源文件作用对比

    头文件声明接口,源文件实现逻辑。头文件含类、函数声明及宏定义,通过#include被多文件共享,用include守卫防重;源文件实现具体功能,编译为目标文件后由链接器合并。声明与实现分离提升模块化与编译效率,模板和内联函数因需编译时可见故常置于头文件,命名空间避免符号冲突,整体结构使项目更清晰易维护…

    2026年5月10日
    000
  • C++ 函数重载在事件驱动的编程中的应用

    在事件驱动的编程中,函数重载可创建具有不同参数签名的相似功能,为单一函数名提供多样化功能。它包含以下优点:代码可读性:使用单一函数名表示相关任务。可维护性:避免重复编写类似逻辑。可重用性:跨项目和应用程序 reutilizar。 C++ 函数重载在事件驱动的编程中的应用 在事件驱动的编程中,函数重载…

    2026年5月10日
    000
  • C++ 函数性能优化对系统稳定性的影响

    标题:C++ 函数性能优化对系统稳定性的影响 简介 函数性能优化是 C++ 程序员提高程序效率的关键技术。本文将探讨函数性能优化对系统稳定性的影响,并提供实战案例来证明这一点。 性能优化对稳定性的作用 立即学习“C++免费学习笔记(深入)”; 函数性能优化不仅可以提升程序速度,还可以提高系统的稳定性…

    2026年5月10日
    000
  • WebAssembly中导入JavaScript函数:无胶水代码集成指南

    本文深入探讨了在WebAssembly模块中直接导入和使用JavaScript函数的机制,特别是当使用Emscripten的STANDALONE_WASM和SIDE_MODULE编译模式时。文章详细分析了TypeError: import object field ‘GOT.mem&#8…

    2026年5月10日
    000
  • C++如何编译和链接_C++从源码到可执行文件的过程解析

    c++kquote>预处理展开宏和头文件,编译生成汇编代码,汇编转为机器码,链接合并目标文件与库生成可执行程序。 当你写完一段C++代码,比如一个简单的hello world程序,最终能运行起来,背后其实经历了一系列步骤:预处理、编译、汇编和链接。这个过程将人类可读的源码转换成机器可以执行的程…

    2026年5月10日
    000
  • c++中sizeof运算符的用法和常见陷阱 _c++ sizeof使用技巧及陷阱解析

    sizeof运算符在编译时计算类型或对象的字节大小,返回size_t类型,常用于获取数据大小、数组元素个数及内存操作;但存在数组传参退化为指针导致失效、对指针无法获知动态内存大小、表达式不求值、结构体因对齐产生填充等常见陷阱;需结合模板、显式传参、对齐控制等方式规避问题,提升代码可移植性和安全性。 …

    2026年5月10日
    000
  • C#如何进行网络编程?Socket与TCP/IP通信编程实例详解

    C#通过Socket类实现TCP通信,首先服务器绑定IP和端口并监听,客户端发起连接,双方通过Send/Receive收发数据,最后关闭连接。 C# 进行网络编程主要依赖于 System.Net 和 System.Net.Sockets 命名空间,其中最核心的是使用 Socket 类实现基于 TCP…

    2026年5月10日
    000
  • C++ 函数递归详解:递归查找列表中的元素

    递归查找列表元素的步骤如下:递归基础条件:如果列表为空,则元素不存在。递归过程:使用递归调用查找列表的剩余部分,并调整返回的索引。检查列表的第一个元素:如果第一个元素与所查找的元素相等,则元素位于索引 0 处。找不到:如果递归和第一个元素检查都没有找到,则元素不存在。 C++ 函数递归详解:递归查找…

    2026年5月10日
    000
  • C++怎么使用C++17的并行算法库_C++ std::execution与多核性能优化

    c++kquote>C++17通过std::execution策略引入并行算法支持,需编译器(如GCC 8+)和线程库(如TBB)配合;提供seq、par、par_unseq三种策略控制执行模式;可用于sort、for_each等算法提升大数据性能,但需避免数据竞争,推荐使用reduce等安全…

    2026年5月10日
    000
  • c++ lambda表达式怎么写 c++匿名函数用法详解

    答案是lambda表达式可简洁定义匿名函数,用于STL算法等场景。其语法包含捕获列表、参数列表、mutable、返回类型和函数体,如[=](int x) { return x > 0; }可值捕获外部变量并用于判断正数。 在C++中,lambda表达式是一种创建匿名函数的简洁方式,常用于需要传…

    2026年5月10日
    200
  • C++框架的Unlicense许可类型简介

    unlicense 许可证类型为免费且宽松,允许用户在不附加任何限制的情况下使用、修改和分发软件。它旨在最大限度地减少限制和允许最大的自由度,具有以下好处:简洁易懂高度开放无保证 C++ 框架的 Unlicense 许可证类型简介 了解 Unlicense Unlicense 是一个自由和宽松的软件…

    2026年5月10日
    000
  • 利用日志记录增强 C++ 函数的调试能力

    如何利用日志记录增强 c++++ 函数的调试能力?使用 glog 库进行日志记录: 安装 glog,并在代码中使用 glog 头文件和 initgooglelogging() 初始化日志记录。添加日志记录语句: 使用 log() 宏在要记录的代码块中添加日志记录语句,以记录函数开始、结束或其他重要事…

    2026年5月10日
    000
  • C++ 函数模板如何使用并在实际场景中应用?

    函数模板允许您定义可以处理不同类型参数的函数的通用版本。语法为:template,其中 t 是类型参数。要使用函数模板,请指定所需的参数类型,例如:max(10, 20)。函数模板在排序等实际应用中很有用,例如:template void sort(t arr[], int size)。它们具有通用…

    2026年5月10日
    000
  • C++ 并发编程中内存访问问题及解决方法?

    在 c++++ 并发编程中,共享内存访问问题包括数据竞争、死锁和饥饿。解决方案有:原子操作:确保对共享数据的访问是原子性的。互斥锁:一次只允许一个线程访问临界区。条件变量:线程等待某个条件满足。读写锁:允许多个线程并发读取,但只能允许一个线程写入。 C++ 并发编程中的内存访问问题及解决方案 在多线…

    2026年5月10日
    000
  • c++如何实现函数的重载_c++函数重载实现方法

    函数重载通过参数列表差异实现,如类型、数量或顺序不同,编译器根据实参选择对应函数,返回类型不同不能单独用于重载。 在C++中,函数重载允许在同一作用域内定义多个同名函数,只要它们的参数列表不同(参数个数、类型或顺序不同),编译器会根据调用时传入的实参来选择匹配的函数。函数重载不能仅通过返回类型的不同…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信