4.7 C++ Boost 多线程并发库
Boost 库是一个由C/C++语言的开发者创建并更新维护的开源类库,其提供了许多功能强大的程序库和工具,用于开发高质量、可移植、高效的C应用程序。Boost库可以作为标准C库的后备,通常被称为准标准库,是C标准化进程的重要开发引擎之一。使用Boost库可以加速C应用程序的开发过程,提高代码质量和性能,并且可以适用于多种不同的系统平台和编译器。Boost库已被广泛应用于许多不同领域的C++应用程序开发中,如网络应用程序、图像处理、数值计算、多线程应用程序和文件系统处理等。
C++语言并没有对多线程与网络的良好支持,虽然新的C++标准加入了基本的thread
库,但是对于并发编程的支持仍然很基础,Boost库提供了数个用于实现高并发与网络相关的开发库这让我们在开发跨平台并发网络应用时能够像Java等语言一样高效开发。
thread库为C++增加了多线程处理能力,其主要提供了清晰的,互斥量,线程,条件变量等,可以很容易的实现多线程应用开发,而且该库是可跨平台的,并且支持POSIX
和Windows
线程。
7.1 互斥锁
互斥锁通过在访问共享资源的线程之间进行通信来避免并发问题。互斥锁仅允许一个线程在任何给定时间点上访问共享资源。如果已经有一个线程锁定了互斥锁,则任何其他线程都必须等待锁被释放。一旦锁被释放,等待队列中的一个线程将被允许继续其工作。
Boost库中的 boost::mutex 类型表示一个互斥锁。它提供了两个主要函数来控制互斥锁:lock() 和 unlock()。当一个线程想要访问一个共享资源时,它会调用互斥锁的 lock() 函数来获取锁,如果无法获得,线程将最多等待直到锁被释放。在线程访问完共享资源后,它需要调用 unlock() 函数来释放锁,以便其他线程可以获得锁并访问共享资源。
互斥体是用于线程同步的一种手段,其主要用于在多线程环境下,防止多个线程同时操作共享资源,当某线程被锁,其他线程则需要等待它解锁后才能继续访问共享资源。
- thread提供了6种互斥类型,但常用的只有3种:
- mutex 独占互斥锁
- recursive_mutex 递归互斥锁
- shared_mutex 读写锁
通常我们会使用Mutex来保护共享资源,防止在多线程环境中数据的不一致性,当一个资源被锁定,其他线程只能阻塞等待释放后才可继续操作。
#define BOOST_THREAD_VERSION 5
#include <iostream>
#include <boost/thread/thread_guard.hpp>
using namespace std;
using namespace boost;
// 最基本的互斥锁
void MutexA()
{
boost::mutex mutex;
try
{
mutex.lock();
// 执行变量存取操作
mutex.unlock();
}
catch (...)
{
mutex.unlock();
}
}
// 智能互斥锁(无需加解锁)
void MutexB()
{
boost::mutex mutex;
boost::lock_guard<boost::mutex> global_mutex(mutex);
// 只需要定义以上代码即可
}
在Boost中创建多线程非常简单,只需要定义一个MyThread
线程函数,并在主函数中开启线程即可实现。
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
using namespace std;
boost::mutex io_mutex;
void MyThread(int id)
{
for (int i = 1; i < 10; ++i)
{
// 定义智能互斥锁,防止出现输出乱序
boost::mutex::scoped_lock lock(io_mutex);
std::cout << id << ": " << i << std::endl;
}
}
int main(int argc, char *argv[])
{
boost::thread thrd1(boost::bind(&MyThread, 1));
boost::thread thrd2(boost::bind(&MyThread, 2));
//中断线程
thrd1.interrupt();
//获取线程ID
cout << "线程ID:" << thrd1.get_id() << endl;
// 等待线程
thrd1.join();
thrd2.join();
//超过3s结束线程
thrd1.timed_join(boost::posix_time::seconds(3));
thrd2.timed_join(boost::posix_time::seconds(3));
std::system("pause");
return 0;
}
7.2 线程局部存储
Boost库中提供了线程局部存储(Thread Local Storage,简称TLS)的支持,可以让程序中的每个线程都拥有独立的数据空间,互相之间不会受到干扰。这对于一些线程之间需要共享数据,但需要保证数据安全的场景非常有用,例如线程池等。
有时候函数使用了局部静态变量或全局变量,导致无法用于多线程环境,因为无法保证变量在多线程环境下重入的正确操作。
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/tss.hpp>
using namespace std;
// 定义一个全局互斥体
boost::mutex io_mutex;
// 线程本地存储一个整数,声明
boost::thread_specific_ptr<int> ptr;
struct MyThread
{
MyThread(int id) :id(id){}
void operator()()
{
// 如果ptr内部为0则说明没有,我们就初始化为0
if (ptr.get() == 0)
ptr.reset(new int(0));
for (int x = 0; x <10; ++x)
{
// 往自己的线程上加
(*ptr)++;
boost::mutex::scoped_lock lock(io_mutex);
std::cout << "当前ID: " << id << " 本地存储数值: " << *ptr << std::endl;
}
}
public:
int id;
};
int main(int argc, char *argv[])
{
boost::thread thrd1(MyThread(1));
boost::thread thrd2(MyThread(2));
thrd1.join();
thrd2.join();
std::system("pause");
return 0;
}
如果本地存储的类型是一个结构体,如下定义了MyStruct
本地结构体,来实现本地数据累加。
#include <iostream>
#include <string>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/tss.hpp>
using namespace std;
// 定义一个全局互斥体
boost::mutex io_mutex;
// 定义本地存储结构体
typedef struct MyStruct
{
int uid;
std::string uname;
MyStruct(int x, std::string y)
{
uid = x;
uname = y;
}
}MyStruct;
// 线程本地存储一个整数,声明
boost::thread_specific_ptr<MyStruct> ptr;
struct MyThread
{
MyThread(int id) :id(id){}
void operator()()
{
// 如果ptr内部为0则说明没有,我们就初始化为0
if (ptr.get() == 0)
ptr.reset(new MyStruct(0,"lyshark"));
for (int x = 0; x <10; ++x)
{
// 往自己的线程上加
(*ptr).uid = (*ptr).uid + 1;
(*ptr).uname = "lyshark";
boost::mutex::scoped_lock lock(io_mutex);
std::cout << "当前ID: " << id << " 本地存储数值: " << (*ptr).uid << "本地存储名字: " << (*ptr).uname << std::endl;
}
}
public:
int id;
};
int main(int argc, char *argv[])
{
boost::thread thrd1(MyThread(1));
boost::thread thrd2(MyThread(2));
thrd1.join();
thrd2.join();
std::system("pause");
return 0;
}
7.3 使用线程组
线程组thread_group
用于管理一组线程,就像线程池一样,其内部使用了std::list<thread*>
来容纳每个线程对象。
当需要创建新线程时,使用create_thread()
工厂函数,并通过bind
绑定传递参数即可实现创建,如下是最简单的线程组创建。
#include <iostream>
#include <boost/thread.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
using namespace std;
boost::mutex io_mutex;
void MyThread(int x, string str)
{
try
{
// 延时2秒
boost::this_thread::sleep(boost::posix_time::seconds(2));
for (int i = 0; i < x; i++)
{
boost::mutex::scoped_lock lock(io_mutex);
cout << "输出字符串: " << str << " 计次: " << i << endl;
}
}
catch (boost::thread_interrupted&)
{
cout << "thread is interrupt" << endl;
}
}
int main(int argc,char *argv[])
{
// 定义线程组
boost::thread_group group;
for (int x = 0; x < 10; x++)
{
// 创建新线程
group.create_thread(boost::bind(MyThread, x, "hello lyshark"));
}
cout << "当前线程数量: " << group.size() << endl;
group.join_all();
std::system("pause");
return 0;
}
我们还可以通过add_thread
和remove_thread
将特定的线程对象放入到不同的线程组中,来实现对线程的批量操作。
#include <iostream>
#include <string>
#include <boost/thread.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
using namespace std;
typedef struct MyStruct
{
int uuid;
std::string uname;
}MyStruct;
boost::mutex io_mutex;
void MyThread(MyStruct ptr)
{
try
{
for (int i = 0; i < 5; i++)
{
boost::mutex::scoped_lock lock(io_mutex);
cout << "UUID: " << ptr.uuid << " UName: " << ptr.uname << endl;
}
}
catch (boost::thread_interrupted&)
{
cout << "thread is interrupt" << endl;
}
}
int main(int argc,char *argv[])
{
MyStruct my_struct;
boost::thread_group group;
// 创建线程并赋值
my_struct.uuid = 1001;
my_struct.uname = "lyshark";
boost::thread thrd1(&MyThread,my_struct);
my_struct.uuid = 1002;
my_struct.uname = "admin";
boost::thread thrd2(&MyThread, my_struct);
// 将线程加入线程组
group.add_thread(&thrd1);
group.add_thread(&thrd2);
// 中断所有线程
// group.interrupt_all();
// 判断thrd1是否在组内
bool is_in = group.is_thread_in(&thrd1);
std::cout << "是否在组内: " << is_in << std::endl;
// 移除线程组
group.remove_thread(&thrd1);
group.remove_thread(&thrd2);
// 等待线程组执行结束
group.join_all();
boost::this_thread::sleep(boost::posix_time::seconds(2));
std::system("pause");
return 0;
}
7.4 获取线程返回值
获取线程返回值,需要使用异步的方式得到,Boost中提供了ASIO
库来实现异步操作,该库采用了前摄器设计模式,实现了可移植的异步IO操作。
首先来简单的看一下,如何使用异步的方式实现创建线程的。
#define BOOST_THREAD_VERSION 5
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/function.hpp>
using namespace std;
void MyThread(int x)
{
for (int i = 0; i < x; i++)
std::cout << i << std::endl;
}
int main(int argc, char *argv[])
{
// 第一种使用方式
auto x = async(&MyThread, 10);
x.wait();
// 直接通过bind绑定参数
async(boost::bind(MyThread, 20));
// 直接使用lambda表达式
auto y = boost::async([]
{
cout << "hello lyshark" << endl;
});
y.wait();
std::system("pause");
return 0;
}
当我们需要获取单个线程的返回值时,可以使用valid()
方法或使用get()
将返回值从线程里拉取出来。
#define BOOST_THREAD_VERSION 5
#include <iostream>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/function.hpp>
using namespace std;
int MyThread(int x, int y)
{
Sleep(3000);
return x + y;
}
typedef struct
{
int x;
int y;
}MyStruct;
MyStruct MyThreadStruct(int x, int y)
{
MyStruct ref;
ref.x = x + 100;
ref.y = y + 100;
return ref;
}
int main(int argc, char *argv[])
{
// 返回数值直接使用get得到
auto f = boost::async(boost::bind(MyThread, 10, 20));
// auto f = boost::async(boost::launch::async,bind(MyThread, 10, 20));
if (f.valid())
{
cout << "获取计算结果: " << f.get() << endl;
}
f.wait();
// 返回参数是结构体
auto t = boost::async(boost::bind(MyThreadStruct, 100, 200));
MyStruct tmp = t.get();
cout << "获取结构体参数A: " << tmp.x << " 参数B: " << tmp.y << endl;
t.wait();
std::system("pause");
return 0;
}
有时候我们会一次性创建
多个线程共同执行,此时想要获取到每个线程
中的返回值
,那么就需要使用多个future
对象,代码如下。
#define BOOST_THREAD_VERSION 5
#include <iostream>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/function.hpp>
using namespace std;
int MyThread(int x, int y)
{
Sleep(3000);
return x + y;
}
int main(int argc, char *argv[])
{
std::vector<boost::future<int>> vect;
// 启动异步线程,并放入vector容器中
for (int i = 0; i < 100; ++i)
{
vect.push_back(boost::async(bind(MyThread, i, i * 10)));
}
// 等待所有线程计算结束
boost::wait_for_all(vect.begin(), vect.end());
for (auto &x : vect)
{
// 获取到返回值
if (x.valid())
{
cout << "线程计算结果: " << x.get() << endl;
}
}
std::system("pause");
return 0;
}
返回数值类型如果不够存储的话,那么我们可以定义一个MyStruct
结构体,通过结构体传递参数,并将计算结果返回为结构
体类型。
#define BOOST_THREAD_VERSION 5
#include <iostream>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/function.hpp>
using namespace std;
typedef struct
{
int x;
int y;
}MyStruct;
// 定义一个返回结构体的函数
MyStruct MyThreadStruct(int x, int y)
{
MyStruct ref;
ref.x = x + 100;
ref.y = y + 100;
return ref;
}
int main(int argc, char *argv[])
{
std::vector<boost::future<MyStruct>> vec;
for (int x = 0; x < 100; x++)
{
vec.push_back(boost::async(bind(MyThreadStruct, x, x + 10)));
}
boost::wait_for_all(vec.begin(), vec.end());
for (auto &x : vec)
{
MyStruct tmp = x.get();
cout << "获取计算结果A: " << tmp.x << " 获取结果B: " << tmp.y << endl;
}
std::system("pause");
return 0;
}
由于future
只能get
获取一次数据,使得它不能被多线程并发访问,所以就出现了shared_future
,它是future
的增强,可以线程安全的多次调用get()
获取到计算结果,修改很简单只需要将声明改一下,其他的不用动。
void Async()
{
std::vector<boost::shared_future<MyStruct>> vec;
for (int x = 0; x < 100; x++)
{
vec.push_back(boost::async(bind(MyThreadStruct, x, x + 10)).share());
}
boost::wait_for_all(vec.begin(), vec.end());
for (auto &x : vec)
{
MyStruct tmp = x.get();
cout << "获取计算结果: " << tmp.x << endl;
}
}
7.5 共享锁
shared_mutex(共享互斥锁)是 C++11 标准库中引入的一种线程同步机制,可以实现同时有多个线程同时读取共享资源,但只能有一个线程写入共享资源的机制。与常见的互斥锁不同,shared_mutex 具有更加细致的控制对共享资源的访问权限。
该锁允许线程获取多个共享所有权和一个专享所有权,实现了读写锁机制,即多个读线程一个写线程。
#define BOOST_THREAD_VERSION 5
#include <iostream>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/function.hpp>
using namespace std;
using namespace boost;
class MyClass
{
private:
// 读写的数据
int m_x;
// 定义共享互斥量
boost::shared_mutex rw_mutex;
public:
MyClass()
{
m_x = 0;
}
// 写数据
void write()
{
//写锁定
boost::unique_lock<boost::shared_mutex> g(rw_mutex);
++m_x;
}
// 读数据
void read(int *x)
{
// 读锁定
boost::shared_lock<boost::shared_mutex> g(rw_mutex);
*x = m_x;
}
};
// 定义写函数,每次调用都会写入十次
void writer(MyClass &ptr)
{
for (int x = 0; x < 10; x++)
{
ptr.write();
}
}
// 定义读函数,每次调用读取十次
void reader(MyClass &ptr)
{
int item;
for (int x = 0; x < 10; x++)
{
ptr.read(&item);
std::cout << "读取数据: " << item << std::endl;
}
}
int main(int argc, char *argv[])
{
MyClass ptr;
thread_group pool;
// 定义2个读
pool.create_thread(boost::bind(reader, boost::ref(ptr)));
pool.create_thread(boost::bind(reader, boost::ref(ptr)));
// 定义1个写
pool.create_thread(boost::bind(writer, boost::ref(ptr)));
pool.join_all();
std::system("pause");
return 0;
}
7.6 获取线程ID号
实现线程池,每次将一个线程service_io
存入到栈中,需要时从栈中弹出并调用内部相应的函数。
#include <stack>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
using namespace std;
using namespace boost;
class ThreadPool
{
static int count;
int NoOfThread;
int counter;
thread_group group;
boost::mutex mutex_;
asio::io_service io_service;
stack<boost::thread*> thread_stack;
public:
ThreadPool(int num)
{
NoOfThread = num;
counter = 0;
boost::mutex::scoped_lock lock(mutex_);
if (count == 0)
count++;
else
return;
// 开辟线程并放入栈中存储
for (int i = 0; i<num; ++i)
{
thread_stack.push(group.create_thread(boost::bind(&asio::io_service::run, &io_service)));
}
}
~ThreadPool()
{
io_service.stop();
group.join_all();
}
// 从栈中弹出元素
boost::thread* get_thread()
{
// 判断成立说明不存在线程
if (counter > NoOfThread)
return NULL;
// 否则从栈中弹出
counter++;
boost::thread* ptr = thread_stack.top();
thread_stack.pop();
return ptr;
}
// 获取到元素计数器
int get_number()
{
return counter;
}
};
int ThreadPool::count = 0;
int main(int argc, char * argv[])
{
// 定义启动10个线程
ThreadPool pool(10);
// 循环线程池
for (int x = 0; x < 10; x++)
{
boost::thread* ptr = pool.get_thread();
cout << "线程ID: " << ptr->get_id() << endl;
int num = pool.get_number();
std::cout << "计数器: " << num << std::endl;
}
std::system("pause");
return 0;
}
4.7 C++ Boost 多线程并发库的更多相关文章
- 03.Java多线程并发库API使用2
1.多个线程之间共享数据的方式探讨 1.如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做. 2.如果每个线程执行的代 ...
- java——多线程并发库
JDK5中增加了Doug Lea的并发库,这一引进给Java线程的管理和使用提供了强大的便利性. java.util.current包中提供了对线程优化.管理的各项操作,使得线程的使用变得的心应手.该 ...
- 02.Java多线程并发库API使用
1. 传统线程技术回顾 继承线程与实现Runnable的差异?为什么那么多人都采取第二种方式? 因为第二种方式更符合面向对象的思维方式.创建一个线程,线程要运行代码,而运行的代码都封装到 ...
- boost的并发库
thread: http://www.boost.org/doc/libs/1_61_0/libs/thread/ asio: http://www.boost.org/doc/libs/1_61_0 ...
- 04.Java多线程并发库API使用3
1.java5的Semaphere同步工具 Semaphore可以维护当前访问自身的线程个数,并提供了同步机制.使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数 ...
- 【C/C++学院】0904-boost智能指针/boost多线程锁定/哈希库/正則表達式
boost_array_bind_fun_ref Array.cpp #include<boost/array.hpp> #include <iostream> #includ ...
- java的多线程和并发库
一.多线程基础知识 1.传统使用类Thread和接口Runnable实现 1)在Thread子类覆盖的run方法中编写运行代码 2)在传递给Thread对象的Runnable对象的run方法中编写代码 ...
- Java多线程与并发库高级应用-java5线程并发库
java5 中的线程并发库 主要在java.util.concurrent包中 还有 java.util.concurrent.atomic子包和java.util.concurrent.lock子包 ...
- 并发库应用之十 & 多线程数据交换Exchanger应用
申明:用大白话来说就是用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人会一直等待第二个人,直到第二个人拿着数据到来时,才能彼此交换数据. java.util ...
- Java多线程(三) —— 线程并发库之总体架构
对java并发库一直觉得很神秘,决定好好研究一下. 参考文献: https://blog.csdn.net/hp910315/article/details/50963095 http://www.b ...
随机推荐
- spring中这些编程技巧,真的让我爱不释手
前言 最近越来越多的读者认可我的文章,还是挺让人高兴的.有些读者希望我多分享spring方面的知识点,能够在实际工作中派的上用场.我对spring的源码有过一定的研究,结合我这几年实际的工作经验,把s ...
- 技术文档丨 OpenSCA技术原理之npm依赖解析
本文主要介绍基于npm包管理器的组件成分解析原理. npm介绍 npm(全称Node Package Manager)是Node.js标准的软件包管理器. npm的依赖管理文件是package.jso ...
- python进阶(8)--测试函数与类
一.测试函数(unittest) 1.源文件方法(name_function.py): def get_formatted_name(first,last): """生成 ...
- OpenStack 工作流组件: Mistral
1 Mistral 简介 Mistral 是由 Mirantis 开发,贡献给 OpenStack 社区的工作流组件,它提供 Workflow As a Service 服务. 在计算机中通常处理的任 ...
- python3查看文件是否存在,以及读、写与执行的属性
技术背景 在使用python对系统文件进行操作的项目中,经常需要用到对本地文件的存在和读写进行判断的操作.最常用的比如os.exists函数,可以很方便的判断给定的文件名是否存在于系统中.但是这里我们 ...
- Keep English Level-04
firm -- 坚定的,坚固的;公司 share -- n 股份,份额 executive -- 执行官 There is no chance,no density,no fate,that can ...
- 使用 golang 开发 PHP 扩展
使用 golang 开发 PHP 扩展 环境 golang go1.19.9 darwin/arm64 Macos/Linux PHP8.1.11 编译安装 实战 PHP脚手架生成 进入PHP源码,使 ...
- [转帖]Kafka之ack机制
前言 之前的博客里说了,Kafka的消息同步是一种ISR机制,本质上是"完全同步"的一种优化. 都在说,消息被ISR中所有副本都写入才算写入成功.但是这样未免定的太死板了,所以,K ...
- [转帖]备份VCSA内置Postgresql数据库
首先命令行远程登录到VCSA服务器,然后执行如下命令停掉VCSA的核心服务vmware-vpxd: vCenterServerAppliance:~ # service vmware-vpxd sto ...
- [转帖]shell命令替换~date用法~如果被替换命令的输出内容包括多行或有多个连续的空白符,输出变量时应该将变量用双引号包围
https://www.cnblogs.com/mianbaoshu/p/12069458.html Shell 命令替换是指将命令的输出结果赋值给某个变量.比如,将使用ls命令查看到的某个目录中的内 ...