C++11 多线程库使用说明

  1. 多线程基础
    1.1 进程与线程
    根本区别:
    进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位

开销方面:
每个进程都有自己独立的代码和数据空间,程序之间的切换开销较大。
线程可以看作是轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换开销小。

所处环境:
一个操作系统能同时运行多个进程(程序)。
在一个进程中,可以有多个线程同时执行。

内存分配方面:
系统在运行的时候会为每个进程分配不同的内存空间。
对线程而言,系统不会为线程分配内存(线程使用的资源,来自于其所属进程的资源),线程组之间只能共享资源。

包含关系:
没有线程的进程可以看作是单线程。一个进程可以包含多个线程,每个进程有且只有一个主线程
线程是进程的一部分,所以线程也称为轻量级进程-

1.2 并发与并行
如果某个系统支持两个或者多个动作(Action)同时存在,那么这个系统就是一个并发系统。如果某个系统支持两个或者多个动作同时执行,那么这个系统就是一个并行系统。

并行”概念是“并发”概念的一个子集。也就是说,你可以编写一个拥有多个线程或者进程的并发程序,但如果没有多核处理器来执行这个程序,那么就不能以并行方式来运行代码。因此,凡是在求解单个问题时涉及多个执行流程的编程模式或者执行行为,都属于并发编程的范畴。

并发是不是一个线程,并行是多个线程?
答:并发和并行都可以是多个线程,就看这些线程能不能同时被(多个)cpu(物理线程)执行,如果可以就是并行,而并发誓多个线程被 cpu 轮流切换着执行。

进程并发
线程并发
1.3 进程通信
同一台PC:管道,文件,消息队列,共享内存
不同PC:socket
1.4 线程通讯
锁机制:包括互斥锁、条件变量、读写锁
互斥锁提供了以排他方式防止数据结构被并发修改的方法。
读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

信号量机制(Semaphore)
包括无名线程信号量和命名线程信号量。

信号机制(Signal)
类似进程间的信号处理。

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

1.5 线程并发
一个进程中的所有线程共享地址空间,因此全局变量,指针,引用可以在线程之间传递
数据一致性问题
线程之间切换需要保存中间量,消耗资源,所以线程不是越多越好,极限差不多在 2000 个线程,或者根据硬件,比如 CPU*2,或者根据业务需要,实际中一般不操作500个,控制在 200 个以内,否则效率太低。
2. C++ 11 多线程库
c++ 11 开始语言本身提供多线程支持,因此可以实现跨平台,可移植性。

2.1 创建线程

include

初始函数,函数结束,他的所有线程结束
主线程结束,它的子线程结束
#include
#include

using namespace std;

// 入口函数
void entry(int a)
{
cout << “entry sub-thread” << endl;
}

int main()
{
thread threadObj(entry, 6); //入口函数,参数
threadObj.join(); //等待线程结束
// threadObj.detch();
cout << “entry main thread” << endl;
return 0;
}

类的成员函数作为入口函数

class A {
public:
A(int i) : m_i(i)
{
cout << "construct! thread ID: " << std::this_thread::get_id() << endl;
}

void print(const A& a)
{cout << "sub_thread ID: " << std::this_thread::get_id() << endl;
}
int m_i;

};

int main()
{
int n = 1;
int& m = n;
A a(10);
cout << "main thread ID: " << std::this_thread::get_id() << endl;

thread mythread(&A::print, &a, a);  //传入成员函数地址、 类对象地址、参数
mythread.join();
return 0;

}

2.2 thread 的入口
可调用对象(普通函数,类成员函数,类静态函数,仿函数,函数指针,重载了operate ()的类对象,lambda表达式,std::function)
class 需要是可调用的类,即 void operator ()(), 注意以对象作为入口,对象会被复制到子线程中
2.3 thread 入口参数
引用 vs实测背后发生了copy,所以无法通过引用来传值,需要注意的是,如果用引用需要同时用const,比如const A& a, 否则会报错,如果需要通过引用传值,需要用std::ref(或者加 & )
void print(const int& n) //没有const会报错
{
cout << "sub_thread: " << n << endl;
}

int main()
{
int n = 1;
int& m = n;

thread mythread(print, m); //虽然print参数是引用,m 是引用但是会发生拷贝
//thread mythread(print, std::ref(m)) //引用
mythread.join();
return 0;

}

指针 不安全,可能主线程已经销毁了内存,造成隐患,detach时一定会出问题

临时参数(对象)可以帮助解决主线程退出的问题,即主线程退出之前会先构造好临时对象,具体来说,在创建线程时就构造临时对象,然后在线程入口函数里面用引用来接(否则会多一次拷贝构造)

如果用隐式类型转换会有风险,因为隐式转换会在子线程中完成,如果detach的话,就会线程不安全

如果参数是智能指针,如unique_ptr, 需要用std::move(your unique_ptr), 但是一定要用join,因为内存是共享的,否则会不安全

成员函数指针

2.4 多个线程下保护共享数据
2.4.1 mutex
#include “stdafx.h”
#include
#include
#include
#include
#include

using namespace std;

class Msg {
public:
void InMsg()
{
for(int i = 0; i < 1000; ++i)
{
cout << "start input msg id = " << i << endl;
mut_Msg.lock();
m_Msg.push_back(i);
mut_Msg.unlock();
cout << "end input msg id = " << i << endl;
}
}

void OutMsg()
{while (1){if (bOutMsg()){cout << "pop out msg success!" << endl;}else{cout << "msg box is empty!" << endl;_sleep(1000);}}
}bool bOutMsg()
{mut_Msg.lock();if (!m_Msg.empty()){m_Msg.pop_front();mut_Msg.unlock();return 1;}else{mut_Msg.unlock();return 0;}
}

private:
list m_Msg;
mutex mut_Msg;
};

int main(void)
{
Msg a;
thread thread1(&Msg::InMsg, &a);
thread thread2(&Msg::OutMsg, &a);
thread1.join();
thread2.join();
return 0;
}

mutex 使用时应该尽量只保护需要保护的代码段。
unlock 不能丢
2.4.2 lockguard
可以用 lockguard 接管 mutex,这样就不用手动 unlock, 传入 std::adopt_lock 参数就是告诉 lockguard,锁已经锁了,只需要管理 unlock 就可以了。

lockguard 实际上是在其构造函数中调用了 lock(), 析构函数中调用 unlock().

std::lock(mutex1, mutex2);
std::lockguardstd::mutex guard1(mutex1, std::adopt_lock)
std::lockguardstd::mutex guard2(mutex2, std::adopt_lock)
//…

lockguard 没有提供手动 lock & unlock 的接口。

2.4.3 死锁
死锁,两个或以上的 lock 可能出现死锁。

threadA
{
mutexA.lock();
mutexB.lock();
//do some thing
mutexA.unlock();
mutexB.unlock();
}

threadB
{
mutexB.lock();
mutexA.lock();
//do some thing
mutexA.unlock();
mutexB.unlock();
}

解决方法:

可以通过控制不同的线程lock的顺序来避免
std::lock() 同时锁多个锁,如果有一个锁不上,它会 unlock 已经 lock 的锁
std::lock(mutex1, mutex2);
//…
mutex1.unlock();
mutex2.unlock();

2.4.4 unique_lock
unique_lock 比 lockguard 更灵活,但是效率要低一些,占用内存更多。

unique_lock 可以取代 lockguard,但是相比 lockguard,有更丰富的一些功能。

unique_lock 支持以下参数:

adopt_lock,意义和 lockguard 中一样表示已经lock了
try_to_lock, 意味着 lock 和 unlock 都自动管理,可以判断是否lock成功,做不同操作,所以不会卡住。
std::unique_lockstd::mutex guard1(mutex1, std::try_to_lock);
if(guard1.owns_lock()) // get lock
{
//…
}
else
{
//…
}

defer_lock, 初始化一个没有加锁的 lock, 可以手动 lock,手动或者自动 unlock,
手动 lock 和 unlock 可以直接调用 unique_lock 的成员函数:lock(), unlock()
unique_lock 可以通过 release 来释放资源,即不再关联mutex。

unique_lock 和 lock_guard 都不能复制,但是unique_lock 的所有权可以转移。

std::unique_lockstd::mutex guard1(_mu);
std::unique_lockstd::mutex guard2 = guard1; // error
std::unique_lockstd::mutex guard2 = std::move(guard1); // ok

2.5 线程安全的单例模式
#include
#include
#include

using namespace std;

std::mutex instance_mutex;

class SP {
private:
SP() {}
static SP *m_pInstance; //static使得 m_pInstance 的作用域到程序结束

class FREE {	//这个class专门负责 delete
public:~FREE(){if (SP::m_pInstance){delete SP::m_pInstance;SP::m_pInstance = NULL;}}
};

public:
static SP* GetInstance() //static,否则无法直接调用
{
if (m_pInstance == NULL) //双重锁定,提高运行效率,减少不必要的lock,unlock
{
std::unique_lockstd::mutex mutex1(instance_mutex); //c++ 11,自动lock,unlock
if (m_pInstance == NULL)
{
m_pInstance = new SP();
static FREE f; //static 表示作用域直到程序推出,也就是说程序退出时会调用析构函数,从而达到自动释放内存的作用
}
return m_pInstance;
}
}

static void Free()	//手动 delete
{if (m_pInstance){delete m_pInstance;m_pInstance = NULL;}
}

};

SP* SP::m_pInstance = NULL;

int main(void)
{
SP *p1 = SP::GetInstance();
SP *p2 = SP::GetInstance();
SP::Free();
SP::Free();
p1->Free();
p2->Free();
}

2.6 call_once()
std::call_once() 是 c++ 11 引入的函数,保证某个函数只执行一次,具备互斥量的功能,但是比 mutex 高效, 适合比如 init 等场合

std::once_flag g_flag //决定 call_once 是否调用function

std::call_once(g_flag, function_with_code_only_call_once)

2.7 condition_variable
利用 condition_variable 一般用来等待 unique_lock, 可以提高程序执行效率。

condition_variable 类有三个成员函数

wait() 等待一个条件成立
notify_one() 随机唤醒一个正在 wait 的线程,如果线程没有阻塞在 wait() 处,则没有办法唤醒
notify_all() 唤醒所有等待的线程
//thread A
std::unique_lockstd::mutex lock1(mutex1);

//do some thing

condition1.notify_one();
//condition1.notify_all();

//thread B
std::unique_lockstd::mutex lock1(mutex1);

//do some thing

condition1.wait(lock1); //如果没有第二个参数,wait将 release mutex1,然后阻塞,等待被唤醒
//condition1.wait(lock1, [this]{ //第二个参数可以是任何可调用对象,如果表达式返回ture,直接return,如果为fasle,同上
if(m_bStatus)
{
return true;
}
return false;
})

虚假唤醒
线程被notify,但是却并没发执行,比如上面的例子,m_bStatus 为false, 所以通过第二个参数可以防止虚假唤醒。
2.8 std::async, std::future
之前通过 thread 来创建线程,如果需要返回结果,可以通过全局变量/引用来实现,这里是另一种方式。

async 用于启动一个异步任务(创建线程并执行入口函数),返回一个 future 对象。通过 future 对象的 get() 获取入口函数返回的结果。

int entry()
{
//。。。
return 1;
}

int main()
{
std::future result = std::async(entry); //entry 开始执行
//std::future result = std::async(std::launch::async, entry); //效果同上
//std::future result = std::async(std::launch::deferred, entry); //延迟创建,等待 get/wait 才开始执行,如果没有调用,不会创建子线程
int re = reult.get(); //get() 时会等待 entry 执行完, get() 不能调用多次
//result.wait(); //不获取值返回值,等待线程
}

2.9 std::packaged_task
包装可调用对象,方便作为线程入口调用。

int entry(int a)
{
//。。。
return a;
}

int main()
{
std::packaged_task<int(int)> pt(entry); //pt 本身就是一个可调用对象,类似函数,可以直接 pt(10),调用
std::thread thread1(std::ref(pt), 1);
thread1.join()

std::future<int> result = pt.get_future(); //result 保存返回结果,可以 get
//。。。

}

2.10 std::promise
可以通过promise在线程直接传递值,一个线程往 promise 对象中写值,在其他线程中取值

void entry(std::promise &prom, int a)
{
//。。。
prom.set_value(result)
return;
}

void entry(std::future & f)
{
//。。。
result = f.get();
return;
}

int main()
{
std::promise prom;
std::thread t1(entry, std::ref(prom), 10);
t1.join()

std::future<int> result = prom.get_future(); //result 保存返回结果,可以 get//。。。

}

2.11 atomic
atomic 作用和 mutex 类似,不同点是 mutex 针对一个代码段,而 atomic 针对一个变量。
atomic 操作相比 mutex 效率更高。

int g_count = 0;
//mutex
void entry()
{
mutex1.lock();
g_count++;
mutex1.unlock;
}

std::atomic g_count = 0;
//atomic
void entry()
{
g_count++;
}

2.12 windows 临界区
windows 临界区的概念和 mutex 类似。另外多次进入临界区是OK的,但是需要调用对应次数的出临界区。mutex 是不允许同一个线程中多次 lock 的。

include <windows.h>

CRITICAL_SECTION winsec
InitializeCriticalSection(winsec) //使用前必须初始化

EnterCriticalSection(&winsec);
EnterCriticalSection(&winsec);
//do some thing
LeaveCriticalSection(&winsec);
LeaveCriticalSection(&winsec);

2.13 线程池
#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include
#include
#include
#include
#include
#include <condition_variable>
#include
#include
#include

// 线程池类
class ThreadPool {
public:
// 构造函数,传入线程数
ThreadPool(size_t threads);
// 入队任务(传入函数和函数的参数)
template<class F, class… Args>
auto enqueue(F&& f, Args&&… args)
->std::future<typename std::result_of<F(Args…)>::type>;
// 一个最简单的函数包装模板可以这样写(C++11)适用于任何函数(变参、成员都可以)
// template<class F, class… Args>
// auto enqueue(F&& f, Args&&… args) -> decltype(declval()(declval()…))
// { return f(args…); }
// C++14更简单
// template<class F, class… Args>
// auto enqueue(F&& f, Args&&… args)
// { return f(args…); }

// 析构
~ThreadPool();

private:
// need to keep track of threads so we can join them
// 工作线程组
std::vector< std::thread > workers;
// 任务队列
std::queue< std::function<void()> > tasks;

// synchronization 异步
std::mutex queue_mutex;	// 队列互斥锁
std::condition_variable condition;	// 条件变量
bool stop;	// 停止标志

};

// the constructor just launches some amount of workers
// 构造函数仅启动一些工作线程
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for (size_t i = 0; i<threads; ++i)
// 添加线程到工作线程组
workers.emplace_back( // 与push_back类型,但性能更好(与此类似的还有emplace/emlace_front)
[this]
{ // 线程内不断的从任务队列取任务执行
for (;😉
{
std::function<void()> task;

		{// 拿锁(独占所有权式)std::unique_lock<std::mutex> lock(this->queue_mutex);// 等待条件成立this->condition.wait(lock,[this] { return this->stop || !this->tasks.empty(); });// 执行条件变量等待的时候,已经拿到了锁(即lock已经拿到锁,没有阻塞)// 这里将会unlock释放锁,其他线程可以继续拿锁,但此处任然阻塞,等待条件成立// 一旦收到其他线程notify_*唤醒,则再次lock,然后进行条件判断// 当[return this->stop || !this->tasks.empty()]的结果为false将阻塞// 条件为true时候解除阻塞。此时lock依然为锁住状态// 如果线程池停止或者任务队列为空,结束返回if (this->stop && this->tasks.empty()) {return;}// 取得任务队首任务(注意此处的std::move)task = std::move(this->tasks.front());// 从队列移除this->tasks.pop();}// 执行任务task();}
}
);

}

// add new work item to the pool
// 添加一个新的工作任务到线程池
template<class F, class… Args>
auto ThreadPool::enqueue(F&& f, Args&&… args)
-> std::future<typename std::result_of<F(Args…)>::type>
{
using return_type = typename std::result_of<F(Args…)>::type;

// 将任务函数和其参数绑定,构建一个packaged_task
auto task = std::make_shared< std::packaged_task<return_type()> >(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
// 获取任务的future
std::future<return_type> res = task->get_future();
{// 独占拿锁std::unique_lock<std::mutex> lock(queue_mutex);// don't allow enqueueing after stopping the pool// 不允许入队到已经停止的线程池if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}// 将任务添加到任务队列tasks.emplace([task]() { (*task)(); });
}
// 发送通知,唤醒某一个工作线程取执行任务
condition.notify_one();
return res;

}

// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
{
// 拿锁
std::unique_lockstd::mutex lock(queue_mutex);
// 停止标志置true
stop = true;
}
// 通知所有工作线程,唤醒后因为stop为true了,所以都会结束
condition.notify_all();
// 等待所有工作线程结束
for (std::thread &worker : workers) {
worker.join();
}
}

#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/256708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

005 vim程序编辑器

1. 为何要学 vim 所有的 Unix Like 系统都会内建 vi 文本编辑器&#xff0c;其他的文书编辑器则不一定会存在&#xff1b;很多个别软件的编辑接口都会主动呼叫 vi (例如未来会谈到的 crontab, visudo, edquota 等指令)&#xff1b;vim 具有程序编辑的能力&#xff0c;可以主动…

Redis 实践笔记1---基础知识

前言 由于Redis的广泛使用&#xff0c;加上在项目中涉及到Redis&#xff0c;因此会根据自己的使用和学习&#xff0c;写一个Redis系列的博客&#xff0c;作为自己的笔记&#xff0c;同时也分享给大家。 What is Redis ? 看一下Redis的官网http://redis.io/对REDIS的描述&#…

[操作系统实验lab4]实验报告

实验概况 在开始实验之前&#xff0c;先对实验整体有个大概的了解&#xff0c;这样能让我们更好地进行实验。 我们本次实验需要补充的内容包括一整套以sys开头的系统调用函数&#xff0c;其中包括了进程间通信需要的一些系统调用如sys_ipc_can_recv等&#xff0c;以及补充完成f…

设计模式C++实践

1、单例模式 1&#xff09;饿汉士单例模式 2&#xff09;懒汉士单例模式

js中怎么写自执行函数

<!DOCTYPE html><html lang"en"><head> <meta charset"UTF-8"> <title></title> <script type"text/javascript"> /** * Window 是一个非常重要的对象 */ c…

最优化课堂笔记06-无约束多维非线性规划方法(含重点)

引言 6.1 坐标轮换法&#xff08;工程上基本不用&#xff0c;效率低不适用高维&#xff09; 6.1例子&#xff1a;主要是对最优步长alpha的确定 6.2 最速下降法&#xff08;相邻两次的搜索方向互相垂直&#xff09; 6.2例子 求解法一 注&#xff1a;最速下降法与坐标轮换法的区…

WINHEX的比较、同步功能加上NTFS对稀疏文件的支持

[原创]如何快速地分析RAID信息在每块盘上的记录方式&#xff0c;如何快速地确定系统的实质读写操作。WINHEX是一个非常好的软件&#xff0c;通过其比较和同步功能加上NTFS对稀疏文件的支持&#xff0c;看看怎么实现上述设想。。。我们会有这样的需求&#xff1a;在RAID上的几块…

Z表数据EXCEL导入

很多项目都有这种需求&#xff0c;虽然别人用的各有不同&#xff0c;不过闲来无事&#xff0c;还是自己搞了一个出来。基于EXCEL的导入。 *&---------------------------------------------------------------------* *& Report ZLY_UPLOAD_TABLE *& *&------…

运动轨迹规划算法专栏

1、全局局部路径规划&#xff0c;解决低速简单障碍物环境的路径规划功能

现代制造工程课堂笔记06-集成电路制造工程

中国主要是进行了集成电路的下游环节&#xff0c;即是封装与测试

悖论:早期互联网项目,是否需要技术含量?

在自己创业、看别人创业、和别人一起创业的过程中&#xff0c;一直有个“悖论”困惑着我&#xff0c;让我很不舒服。因为如鲠在喉&#xff0c;所以不吐不快。悖论 早期互联网项目&#xff0c;通常来说&#xff0c;技术难度不算大&#xff1f; 从创业者角度来讲&#xff0c;最…

资源打包后项目中的文件

一.资源打包Assets.car1.如果部署版本>8.0,并且图片被放入到Images.xcassets,图片打包之后会被放到Assets.car,并且是有对图片资源进行压缩.2.如果部署版本<8.0,并且图片被放入到Images.xcassets,图片会被放到MainBundle里面,并且不会对图片进行压缩处理.3.如果图片是直接…

现代制造工程-考试复习02

1.说明&#xff1a;标有重点的是会出简答题或者计算题&#xff0c;而未标注的则是会出选择题和填空题 2.题型&#xff1a;判断、选择、简答、论述

VINS状态估计篇-视觉sfm初始化

1、通过判断当前帧和滑动窗口中&#xff0c;平均视差>30

TP_字母函数

1. A() A函数用于实例化控制器 格式&#xff1a;[资源://][模块/]控制器 A($name,$layer,$level) param string $name 资源地址 param string $layer 控制层名称 param integer $level 控制器层次 return Controller|false 2. B() 执行某个行为 B($name,$tag,&$paramsNULL)…

父div高度和宽度的应用

这是我自己在仿腾讯首页时遇到的布局问题&#xff0c;在此记录&#xff0c;如果有错&#xff0c;欢迎指正。 首先是对齐问题&#xff0c;可以把父div的高度设置为0&#xff0c;然后调整padding值&#xff0c;这样可以批量调整子div们和其他父div的相对高度。 这是父div的样式 .…

flume package遇到的问题

flume打包遇到的一些问题 1.ipc兼容性问题&#xff0c;线上使用2.3.0的hdfs&#xff0c;但是打包时默认为1.2.1的 123408 Apr 2015 19:38:25,122 WARN [SinkRunner-PollingRunner-DefaultSinkProcessor] (org.apache.flume.sink.hdfs.HDFSEventSink.process:455) - HDFS IO e…

有限元笔记06-三维实体单元

多个四面体单元组合起来可以形成六面体

python读取excle表格数据,将数据编辑到图像上工程

这里写目录标题一级目录&#xff1a;python读取excel表格内容二级目录&#xff1a;python如何分割字符串三级目录&#xff1a;python如何在图像的相应位置编辑文字或者字母、数字一级目录&#xff1a;python读取excel表格内容 二级目录&#xff1a;python如何分割字符串 三级…