C++并发编程 thread
std::thread
C++11在标准库中为多线程提供组件, 使用线程需要包含头文件 thread, 其命名空间为 std.
启动新线程
每个进程至少有一个线程: 执行main()函数的线程, 其余线程有其各自的入口函数(线程函数)。
当线程执行完线程函数后, 线程也会退出. 如果不传入线程函数(类似这种形式std::thread t;), 线程不会运行. 线程函数不能重载, 否则不能编译.
在为一个线程创建了一个 std::thread 对象后, 如果线程已启动(不传入线程序函数时, 线程不会启动), 必须要明确是加入(join)还是分离线程(detach).
// 启动一个线程:
void MyThread(const std::string& str)
{
PRINT_LINE_INFO();
std::cout << str << std::endl;
}
//std::thread t(MyThread, "Hello C...");
std::thread t([] {
MyThread("Hello C...");
MyThread("Hello C2...");
});
// 对于类方法, 需要使用 std::bind.
std::thread t(std::bind(&ThreadExample::MyThread, this, "msg"));
ThreadGuard tg(t);
如果 std::thread 对象销毁之前还没有调用 join 或 detach, 程序就会终止( std::thread 的析构函数会调用 std::terminate() ). 因此, 即便是有异常存在, 也需要确保线程能够正确的加入(joined)或分离(detached).
调用 join 或 detach 之前需要调用 joinable() 判断一下线程是否运行. 如果 joinable() 返回 false, 则不需要.
join()是简单粗暴的等待线程完成, 此时创建 std::thread 对象的线程(以下称主线程)将被阻塞. 如果在线程启动后到主线程在调用 join() 前的代码中发生了异常, 此时将会导致主线程永远没有机会执行.
针对此问题, 需要使用 RAII 机制来解决, 如创建一个 ThreadGuard 对象, 在析构函数中保证总是可以调用到 join.
#ifndef _THREAD_GUARD_
#define _THREAD_GUARD_ #include <thread> class ThreadGuard
{
public:
ThreadGuard(std::thread& t_) : t(t_){} ~ThreadGuard()
{
if (t.joinable())
{
t.join();
}
} ThreadGuard(const ThreadGuard &) = delete;
ThreadGuard& operator=(const ThreadGuard &) = delete; private:
std::thread& t;
}; #endif // _THREAD_GUARD_
如果是分离线程, 必须保证可访问数据的有效性, 否则会产生未定义的行为, 如同单线程中一个对象被销毁后再访问一样.
处理这种情况的常规方法: 使线程函数的功能齐全, 将数据复制到线程中. 如果使用一个可调用的对象作为线程函数,这个对象就会复制到线程中,而后原始对象就可以销毁. 下面是错误的使用方法示例:
class Func
{
int& i;
public:
Func(int& i_) : i(i_) {}
void operator() ()
{
for (unsigned j = ; j < ; ++j)
{
// 潜在访问隐患:悬空引用 i
std::cout << i << " ";
}
std::cout << std::endl;
}
};
{ // 某个作用域内
int* p = new int();
Func f(*p);
std::thread t(f);
t.detach(); // 不等待线程结束
delete p;
} // 新线程可能还在运行
线程函数
线程函数可以有不同的参数, 向线程传递参数,只要在构造 std::thread 对象时,按照线程函数参数列表一一对应传入即可。线程函数有几点需要注意的地方:
(1) 默认的参数会被拷贝到独立的线程中,即使是引用的形式, 如果需要需要传递引用, 需要使用 std::ref 显示说明(并且线程函数参数也需要声明为引用).
void ThreadParamRef(std::string& str)
{
str += " --> add";
} void ThreadParam(std::string str)
{
str += " --> add";
} std::string str("Hello C++ Thread...");
//std::thread t(ThreadParamRef, str);
std::thread t(ThreadParamRef, std::ref(str)); // 只有这种形式才能在线程执行完毕后输出 Hello C++ Thread... --> add
//std::thread t(ThreadParam, std::ref(str));
t.join();
std::cout << str << std::endl;
(2) 线程参数传递时需要注意不能传入局部变量, 考虑下面的代码,buffer②是一个指针变量,指向本地变量,然后本地变量通过buffer传递到新线程中②。
函数有很大的可能,会在字面值转化成 std::string 对象之前崩溃,从而导致线程的一些未定义行为。
解决方案就是在传递到 std::thread 构造函数之前就将字面值转化为 std::string 对象。
void f(int i,std::string const& s);
void oops(int some_param)
{
char buffer[]; //
sprintf(buffer, "%i",some_param);
std::thread t(f,,buffer); //
t.detach();
}
// 正确的方法
void f(int i,std::string const& s);
void not_oops(int some_param)
{
char buffer[];
sprintf(buffer,"%i",some_param);
std::thread t(f,,std::string(buffer)); // 使用std::string,避免悬垂指针
t.detach();
}
(3) 线程函数参数传递时, 可以移动, 但不能拷贝. "移动"是指: 原始对象中的数据转移给另一对象,而转移的这些数据在原始对象中不再保存.
void ThreadParamUniquePtr(std::unique_ptr<int> up)
{
std::cout << (up.get() ? *up : -) << std::endl;
}
std::thread t(ThreadParamUniquePtr, std::move(up));
//std::thread t(ThreadParamUniquePtr, up); // 不能编译
//std::thread t(ThreadParamUniquePtr, std::ref(up)); // 要求线程函数参数也为引用才能编译
t.join();
std::cout << (up.get() ? *up : -) << std::endl; // 将输出-1
线程所有权转移
线程是资源独占型, 但可以将所有权转移给别的对象. 如果一个 std::thread 对象与一个运行的线程关联, 此时接受一个新的线程所有权时, 其以前关联的线程将直接调用 std::terminate() 终止程序继续运行.
std::thread t1(f);
std::thread t2(f);
// t1 = std::thread(f); // t1 所有权还没有转移, 不能通过赋一个新值来放弃线程
// t1 = std::move(t2); // t1 所有权还没有转移, 不能通过赋一个新值来放弃线程
t1.detach(); 或 t1.join();
t1 = std::move(t2);
std::thread t3 = std::move(t1);
t1 = std::move(t2);
线程对象也可以在函数中进行转移.
std::thread f1()
{
return std::thread(f);
}
std::thread f2()
{
std::thread t(f);
return t;
}
void f3(std::thread t);
void f4()
{
f3(std::thread(f));
std::thread t(f);
f3(std::move(t));
}
由于 std::thread 是可转移的, 如果容器对移动操作支持, 则可以将 std::thread 对象放入其中.
class Func
{
int i;
public:
Func(int i_) : i(i_) {}
void operator() ()
{
for (unsigned j = ; j < ; ++j)
{
std::cout << i << " ";
}
std::cout << std::endl;
}
};
std::vector<std::thread> threads;
for (int i = ; i < ; i++)
{
Func f(i);
//std::thread t(f);
//v.push_back(t); // 不能采用这种方式
//v.push_back(std::move(t)); // 需要使用移动操作才可以
threads.push_back(std::thread(f));
}
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); // 对每个线程调用join()
常用函数
std::thread::hardware_concurrency() 返回 CPU 核心线程数. 如果无法查询系统信息时, 返回0. (static 函数)
get_id() 返回 std::thread 对象关联的线程的 id. 如果所有权已转移, 或线程函数已返回, 返回0.
std::this_thread::get_id() 取得当前线程的 id. (static 函数)
一个更好的ThreadGuard
#ifndef _THREAD_GUARD_
#define _THREAD_GUARD_ template <class _Thread>
class ThreadGuard
{
public:
explicit ThreadGuard(_Thread& t_) : t(t_) {} ~ThreadGuard()
{
if (t.joinable())
{
t.join();
}
} ThreadGuard(const ThreadGuard &) = delete;
ThreadGuard& operator=(const ThreadGuard &) = delete; private:
_Thread& t;
}; #endif // _THREAD_GUARD_
C++并发编程 thread的更多相关文章
- java 并发编程——Thread 源码重新学习
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
- Java并发编程-Thread类的使用
在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知识:线程的几种状态.上下文切换,然后接着 ...
- 并发编程:Thread和Runable-01
1.继承Thread类(不推荐) 代码很简单,就不说了 public class ThreadTest02 { public static void main(String[] args) { n ...
- Java 并发编程:Callable和Future
项目中经常有些任务需要异步(提交到线程池中)去执行,而主线程往往需要知道异步执行产生的结果,这时我们要怎么做呢?用runnable是无法实现的,我们需要用callable实现. import java ...
- Java 并发编程——Executor框架和线程池原理
Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...
- java并发编程——通过ReentrantLock,Condition实现银行存取款
java.util.concurrent.locks包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器.该框架允许更灵活地使用锁和条件,但以更难用的语法为代价. Lock 接口 ...
- Java并发编程——BlockingQueue
简介 BlockingQueue很好的解决了多线程中,如何高效安全"传输"数据的问题.通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利. 阻塞队列是 ...
- Java 并发编程——Callable+Future+FutureTask
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
- Java并发编程——阻塞队列BlockingQueue
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
随机推荐
- 吴恩达 Deep learning 第一周 深度学习概论
知识点 1. Relu(Rectified Liner Uints 整流线性单元)激活函数:max(0,z) 神经网络中常用ReLU激活函数,与机器学习课程里面提到的sigmoid激活函数相比有以下优 ...
- /proc/sys目录下各文件参数说明
linux 其他知识目录 原文链接:https://blog.csdn.net/hshl1214/article/details/4596583 一.前言本文档针对OOP8生产环境,具体优化策略需要根 ...
- Kickstart 安装centos7
以前是怎么安装系统的 光盘(ISO文件,光盘的镜像文件)===>每一台物理机都得给一个光驱,如果用外置光驱的话,是不是每台机器都需要插一下 U盘:ISO镜像刻录到U盘==>需要每台机器都需 ...
- 大前端全栈CSS3移动端开发
作者声明:本博客中所写的文章,都是博主自学过程的笔记,参考了很多的学习资料,学习资料和笔记会注明出处,所有的内容都以交流学习为主.有不正确的地方,欢迎批评指正 本节课学习视频来源:https://ww ...
- Python3 匿名函数
一 匿名函数 lambda函数也叫匿名函数,语法结构如下: lambda x:x+1 x --> 形参 x+1 --> 返回值,相当于return x+1 实例(Python3.0+): ...
- PIGCMS 关闭聊天机器人(小黄鸡)
无脑操作举例 1.找到 WeixinAction.class.php 文件,路径: 你的版本\PigCms\Lib\Action\Home 2.查询 function chat ,在 chat() 函 ...
- CF刷刷水题找自信 2
CF 1114A Got Any Grapes(葡萄)? 题目意思:给三个人分葡萄,三个人对葡萄的颜色有一些要求,问所准备的三种颜色的葡萄能否满足三人的要求. 解题意思:直接按条件判断即可. #in ...
- 马士兵老师hadoop讲解总结博客地址记录(啊啊啊啊啊,自己没有保存写好的博客...)
http://www.cnblogs.com/yucongblog/p/6650822.html
- 周总结<7>
这周和3位朋友一起完成了系运动会的视频,感受很多,也学到很多. 周次 学习时间 新编代码行数 博客量 学到知识点 14 20 100 1 Html页面设计:虚拟机:(C语言)最小生成树与最短路径 Ht ...
- WebService(一)
1.简介 Web service是一个平台独立的,低耦合的,自包含的.基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述.发布.发现.协调和配置这些应用程序,用 ...