1、std::thread

在C++11之前,C++语言层面是不支持多线程的,想利用C++实现并发程序,借助操作系统的API实现跨平台的并发程序存在着诸多不便,当C++11在语言层面支持多线程后,编写跨平台的多线程代码就方便了许多。

C++11提供的std::thread在开发多线程方面带来了便捷。

#include <iostream>
#include <thread>
​
void threadfunc()
{std::cout << "thread func" << std::endl;
}
​
​
int main()
{std::thread t1(threadfunc);t1.join();   //等待threadfunc运行结束return 0;
}
​

首先定义线程对象t1,线程函数threadfunc运行在线程对象t1中,当线程创建成功并执行线程函数后,一定要保证线程函数运行结束才能退出,这里调用了join()函数阻塞线程,直到threadfunc()运行结束,回收对应创建线程的资源。如果不阻塞线程,就不能保证线程对象t1threadfunc()运行期间有效,下面不调用join()阻塞线程。

#include <iostream>
#include <thread>
​
void threadfunc()
{std::cout << "thread func" << std::endl;
}
​
​
int main()
{std::thread t1(threadfunc);//t1.join();   //等待threadfunc运行结束return 0;
}
​

在运行时引起了程序崩溃。

除了调用join()阻塞线程,保证线程对象在线程函数运行期间的有效性,还可以通过线程分离的手段实现,调用detach()函数使得线程对象与线程函数分离,这样,在线程函数运行期间,线程对象与线程函数就没有联系了,此时的线程是作为后台线程去执行,detach()后就无法再和线程发生联系,也不能通过join()来等待线程执行完毕,线程何时执行完无法控制,它的资源会被init进程回收,所以,通常不采用detach()方法。

#include <iostream>
#include <thread>
​
void threadfunc()
{std::cout << " detach thread func" << std::endl;}
​
int main()
{std::thread t1(threadfunc);t1.detach();      //线程分离
​return 0;
}

这里调用detach()实现线程分离,但是运行后,主线程退出的时候threadfunc()还没有输出“detach thread func”threadfunc()什么时候运行结束也无法确定,为了看到所创建的线程运行结果,在主线程等待一下再退出。

#include <iostream>
#include <thread>
#include <chrono>   //时间
​
void threadfunc()
{std::cout << "detach thread func" << std::endl;
}
​
​
int main()
{std::thread t1(threadfunc);t1.detach();while (true){std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒break;}return 0;
}

此时运行结果:

detach thread func

通过std::thread创建的线程是不可以复制的,但是可以移动。

#include <iostream>
#include <thread>
#include <chrono>
​
void threadfunc()
{std::cout << "move thread func" << std::endl;}
​
​
int main()
{std::thread t1(threadfunc);std::thread t2(std::move(t1));t2.join();while (true){std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒break;}return 0;
}

输出结果:

move thread func

移动后t1就不代表任何线程了,t2对象代表着线程threadfunc()。另外,还可以通过std::bind来创建线程函数。

#include <iostream>
#include <thread>
#include <chrono>     //时间
#include <functional>  //std::bind
​
class A {public:void threadfunc(){std::cout << "bind thread func" << std::endl;}
};
​
​
int main()
{A a;std::thread t1(std::bind(&A::threadfunc,&a));t1.join();while (true){std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒break;}return 0;
}
​

创建一个类A,然后再main函数中将类A中的成员函数绑定到线程对象t1上,运行结果:

bind thread func

每个线程都有自己的线程标识,也就是线程ID,当线程创建成功后,可以通过get_id()来获取线程的ID。

#include <iostream>
#include <thread>
#include <chrono>
#include <functional>
​
class A {public:void threadfunc(){std::cout << "bind thread func" << std::endl;}
};
​
​
int main()
{A a;std::thread t1(std::bind(&A::threadfunc,&a));std::cout << "main thread ID is : " << std::this_thread::get_id() << std::endl;std::cout << "t1 thread ID is : " << t1.get_id() << std::endl;t1.join();while (true){std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒break;}return 0;
}

std::this_thread::get_id()获取的是当前线程的ID,t1.get_id()获取的是所创建的t1对象中运行的线程ID,对应的ID分别为:

main thread ID is : 11932
t1 thread ID is : 12076
bind thread func

虽然get_id()可以获取线程的ID,但是其返回类型是thread::id,通过std::cout可以输出线程ID,但是这样使用似乎不太方面,要是能转换为整形就好了。其实可以将得到的线程ID写入到ostreamstring流中,转换成string类型,再转换成整形。

#include <iostream>
#include <thread>
#include <chrono>
#include <functional>
#include <sstream>
​
class A {public:void threadfunc(){std::cout << "bind thread func" << std::endl;}
};
​
​
int main()
{A a;std::thread t1(std::bind(&A::threadfunc, &a));
​std::ostringstream os1;os1 << t1.get_id() << std::endl;std::string strID = os1.str();            //转换成string类型int threadID = atoi(strID.c_str());       //转换成int类型std::cout << "t1 thread ID is : " << threadID << std::endl;
​t1.join();while (true){std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒break;}
​return 0;
}

输出结果:

t1 thread ID is : 6956
bind thread func


2、std::mutex

进入多线程编程的世界,除了要牢牢掌握std::thread使用方法,还要掌握互斥量(锁)的使用,这是一种线程同步机制,在C++11中提供了4中互斥量。

std::mutex;                  //非递归的互斥量
std::timed_mutex;            //带超时的非递归互斥量
std::recursive_mutex;        //递归互斥量
std::recursive_timed_mutex;  //带超时的递归互斥量

从各种互斥量的名字可以看出其具有的特性,在实际开发中,常用就是std::mutex,它就像是一把锁,我们需要做的就是对它进行加锁与解锁。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
​
std::mutex g_mutex;
​
void func()
{​std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::microseconds(1000));std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
​
}
int main()
{std::thread t1(func);std::thread t2(func);std::thread t3(func);std::thread t4(func);std::thread t5(func);
​t1.join();t2.join();t3.join();t4.join();t5.join();
​return 0;
}

创建了5个线程,然后分别调用func()函数,得到结果:

entry func test thread ID is : entry func test thread ID is : 19180
entry func test thread ID is : 3596
13632
entry func test thread ID is : 9520
entry func test thread ID is : 4460
leave func test thread ID is : 13632
leave func test thread ID is : 19180
leave func test thread ID is : leave func test thread ID is : 9520
3596
leave func test thread ID is : 4460

可以看出,并没有按顺序去执行线程函数,后面创建的线程并没有等待前面的线程执行完毕,导致结果混乱,下面用std::mutex进行控制:

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
​
std::mutex g_mutex;
​
void func()
{g_mutex.lock();
​std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::microseconds(1000));std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
​g_mutex.unlock();
}
int main()
{std::thread t1(func);std::thread t2(func);std::thread t3(func);std::thread t4(func);std::thread t5(func);
​t1.join();t2.join();t3.join();t4.join();t5.join();
​return 0;
}

只要线程进入func()函数就进行加锁处理,当线程执行完毕后进行解锁,保证每个线程都能按顺序执行,输出结果:

entry func test thread ID is : 8852
leave func test thread ID is : 8852
entry func test thread ID is : 15464
leave func test thread ID is : 15464
entry func test thread ID is : 17600
leave func test thread ID is : 17600
entry func test thread ID is : 16084
leave func test thread ID is : 16084
entry func test thread ID is : 4156
leave func test thread ID is : 4156

虽然通过lock()unlock()可以解决线程之间的资源竞争问题,但是这里也存在不足。

func()
{//加锁执行逻辑处理;    //如果该过程抛出异常导致程序退出了,就没法unlock//解锁}
​
int main()
{......
}

func()中再执行逻辑处理中程序因为某些原因退出了,此时就无法unlock()了,这样其他线程也就无法获取std::mutex,造成死锁现象,其实在加锁之前可以通过trylock()尝试一下能不能加锁。实际开发中,通常也不会这样写代码,而是采用lock_guard来控制std::mutex

template <class _Mutex>
class lock_guard {
public:using mutex_type = _Mutex;
​explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { _MyMutex.lock();     //构造函数加锁       }
​lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx){ }
​~lock_guard() noexcept{ _MyMutex.unlock();   //析构函数解锁}
​lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete;
​
private:_Mutex& _MyMutex;
};
​

lock_guard是类模板,在其构造函数中自动给std::mutex加锁,在退出作用域的时候自动解锁,这样就可以保证std::mutex的正确操作,这也是RAII(获取资源便初始化)技术的体现。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
​
std::mutex g_mutex;
​
​
void func()
{std::lock_guard<std::mutex> lock(g_mutex);   //加锁
​std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::microseconds(1000));std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
​//退出作用域后,lock_guard对象析构就自动解锁
}
int main()
{std::thread t1(func);std::thread t2(func);std::thread t3(func);std::thread t4(func);std::thread t5(func);
​t1.join();t2.join();t3.join();t4.join();t5.join();
​return 0;
}
​

运行结果:

entry func test thread ID is : 19164
leave func test thread ID is : 19164
entry func test thread ID is : 15124
leave func test thread ID is : 15124
entry func test thread ID is : 2816
leave func test thread ID is : 2816
entry func test thread ID is : 17584
leave func test thread ID is : 17584
entry func test thread ID is : 15792
leave func test thread ID is : 15792


3、std::condition_variable

条件变量是C++11提供的另外一种线程同步机制,通过判断条件是否满足,决定是否阻塞线程,当线程执行条件满足的时候就会唤醒阻塞的线程,常与std::mutex配合使用,C++11提供了两种条件变量。

  • std::condition_variable,配合std::unique_lock<std::mutex>使用,通过wait()函数阻塞线程;
  • std::condition_variable_any,可以和任意带有lock()unlock()语义的std::mutex搭配使用,比较灵活,但是其效率不及std::condition_variable

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

下面利用std::mutexstd::condition_variable实现生产者与消费者模式。

#include <iostream>
#include <condition_variable>
#include <thread>
#include <list>
#include <mutex>
#include <chrono>
​
class CTask {public:CTask(int taskID){this->taskId = taskID;}
​void dotask(){std::cout << "consumer a task Id is " << taskId << std::endl;}
private:int taskId;
};
​
​
std::list<std::shared_ptr<CTask>> g_task;
std::mutex g_mutex;
std::condition_variable g_conv;
​
//生产者线程
void ProdecerFunc()
{int n_taskId = 0;std::shared_ptr<CTask> ptask = nullptr;while (true){ptask = std::make_shared<CTask >(n_taskId); //创建任务{std::lock_guard<std::mutex> lock(g_mutex);g_task.push_back(ptask);std::cout << "produce a task Id is " << n_taskId << std::endl;
​}//唤醒线程g_conv.notify_one();
​n_taskId++;
​std::this_thread::sleep_for(std::chrono::milliseconds(1000));}
}
​
//消费者线程
void ConsumerFunc()
{std::shared_ptr<CTask> ptask = nullptr; while (true){std::unique_lock<std::mutex> lock(g_mutex);while (g_task.empty())  //即使被唤醒还要循环判断一次,防止虚假唤醒{g_conv.wait(lock);}
​ptask = g_task.front();  //取出任务g_task.pop_front();
​if (ptask == nullptr){continue;}ptask->dotask();       //执行任务}
}
​
int main()
{std::thread t1(ConsumerFunc);std::thread t2(ConsumerFunc);std::thread t3(ConsumerFunc);
​std::thread t4(ProdecerFunc);
​t1.join();t2.join();t3.join();t4.join();return 0;
}

创建3个消费者线程,一个生产者线程,当存放任务的std::list为空时,消费者线程阻塞,当生产者线程生产一个任务放入std::list中时候,此时满足条件,条件变量就可以唤醒阻塞的线程去执行任务。

produce a task Id is 0
consumer a task Id is 0
produce a task Id is 1
consumer a task Id is 1
produce a task Id is 2
consumer a task Id is 2
produce a task Id is 3
consumer a task Id is 3
produce a task Id is 4
consumer a task Id is 4
produce a task Id is 5
consumer a task Id is 5
produce a task Id is 6
consumer a task Id is 6
produce a task Id is 7
consumer a task Id is 7
......

条件变量的使用过程可以归纳如下:

  • 拥有条件变量的线消费者程获取互斥锁;
  • 消费者线程循环检查条件是否满足,不满足则阻塞等待,此时释放互斥锁;
  • 当生产者线程产生任务后,调用notify_one()或者notify_all()唤醒阻塞的消费者线程;
  • 当消费者线程被唤醒后再次获得互斥锁去执行任务;

4、thread_local

C++11中提供了thread_localthread_local定义的变量在每个线程都保存一份副本,而且互不干扰,在线程退出的时候自动销毁。

#include <iostream>
#include <thread>
#include <chrono>
​
thread_local int g_k = 0;
​
void func1()
{while (true){++g_k;}
}
​
void func2()
{while (true){std::cout << "func2 thread ID is : " << std::this_thread::get_id() << std::endl;std::cout << "func2 g_k = " << g_k << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(1000));}
​
}
​
int main()
{std::thread t1(func1);std::thread t2(func2);
​t1.join();t2.join();
​return 0;
}

func1()g_k循环加1操作,在func2()每个1000毫秒输出一次g_k的值:

func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
​
......
​

可以看出func2()中的g_k始终保持不变。

c++ 多线程 类成员函数_C++11多线程相关推荐

  1. c ++类成员函数_C ++编程中的数据成员和成员函数

    c ++类成员函数 C ++中的数据成员和成员函数 (Data members and Member functions in C++) "Data Member" and &qu ...

  2. c++ 多线程 类成员函数_多线程(C++/Python)

    多线程(C++/Python) 本文包括一下内容: 通过C++11的标准库进行多线程编程,包括线程的创建/退出,线程管理,线程之间的通信和资源管理,以及最常见的互斥锁,另外对python下多线程的实现 ...

  3. c++ 多线程 类成员函数_为什么我说C/C++程序员都要阅读Redis源码之:通过Redis学习事件驱动设计

    0. 为什么我说C/C++程序员都要阅读Redis源码 主要原因就是『简洁』.如果你用源码编译过Redis,你会发现十分轻快,一步到位.其他语言的开发者可能不会了解这种痛,作为C/C++程序员,如果你 ...

  4. c++类成员函数中调用多线程函数_beginthreadex()

    #include "stdafx.h" #include #include #include using namespace std; class A {public:int n; ...

  5. linux线程创建 类函数吗,linux多线程创建时使用类成员函数作为参数

    实际上所有线程都是用来处理C函数的,而不是C++类成员函数.标准库中提供一个API函数,这个函数以回调函数指针作为线程的执行代码并在单独的线程中调用回调函数.问题是在这样的线程库中不能创建执行对象成员 ...

  6. 如何让API回调你的VC类成员函数而不是静态函数

    首先需要包含一个由yzwykkldczsh同志编写的模板类-----万能多用自适应无限制回调模板(为纪念友人fishskin,此模板又称为H>W模板) /******************** ...

  7. C++普通函数指针和类成员函数指针

    举例1:普通函数指针: int(*fun)(double, int);fun = [](double a, int b) {cout << a << endl;cout < ...

  8. C++类成员函数作回调函数

    前面写了一篇文章 C语言消息注册派发模式 介绍了下我理解的C语言消息派发.因为C语言是函数式语言,写回调函数的时候很简单 参数就是一个简单的函数指针就好了, 那在C++里的时候 就有些不一样了,虽然C ...

  9. C++类成员函数转换成函数对象

    C++中,类的成员函数(member_function)通常不能直接作为函数对象来使用,最常见的就是创建线程时,不能使用非静态的成员函数来初始化一个线程. 这个主要是因为没有传入this指针,而下面的 ...

  10. 详解函数指针和类成员函数指针

    作者:倾夜·陨灭星尘 一.什么是函数指针? 函数指针,顾名思义即指向函数的指针. 如果要问,为什么能用一个指针指向一个函数呢?我觉得要理解这个问题,以及要理解后面的函数指针和类成员函数指针,没有什么比 ...

最新文章

  1. unity5.x Translate平移移动 以及GetComponent获取组件
  2. HTML知识点总结之img、scirpt、link标签
  3. Vue3 + cli4 配置路由
  4. 01-操作数组的方法
  5. [转载] java向匿名内部类传递参数
  6. 阿里巴巴小程序繁星计划专题上线,汇集最优扶持资源与最新资讯!
  7. cvDilate() 图像膨胀
  8. 嵌入式C高质量编程培训心得笔记
  9. Neural Turing Machines-NTM系列
  10. 企业邮箱申请注册流程,10分钟搞定公司企业邮箱
  11. 证书服务器,及申请证书。
  12. python语音识别库kaldi_Kaldi 使用 DFSMN 训练语音模型
  13. 微软 bing 壁纸 每日一图 bing api
  14. CSS中使盒子移动方法总结
  15. mysql 轨迹数据存储_基于Tablestore实现海量运动轨迹数据存储
  16. 递归算法时间复杂度分析
  17. Linux学习记录二——文件导航
  18. 因式分解实现协同过滤-及源码实现
  19. 输入一个整数,判断这个数是否为素数,(素数是除1以外只能被1和他本身整除的自然数)
  20. epub 阅读器 android,如何使一个epub阅读器和显示为android

热门文章

  1. 【滤波器】基于matlab GUI分数延迟滤波器设计【含Matlab源码 1347期】
  2. 【人脸识别】基于matlab GUI PCA人脸二维码识别(带面板)【含Matlab源码 754期】
  3. 人脸检测用什么模型_人脸检测模型:使用哪个以及为什么使用?
  4. ai智能时代教育内容的改变_人工智能正在改变我们的评论方式
  5. linux汇编section标签,Linux内核中常用的汇编
  6. 单片机 多机通讯c语言,【C语言】89c52单片机的多机串口通讯,救助
  7. 单元格排序_Excel中这8种简单实用的排序方法,很多人都还不会用!
  8. 两转变两服务器,两大服变鬼两老服制霸,《魔兽世界》怀旧免转结束后的服务器调查...
  9. linux7.7 离线安装nfs客户端_Linux提权姿势二:利用NFS提权
  10. 第二十三章:触发器和行为(九)