参考:
书《深入应用C++11:代码优化与工程级应用》 祁宇
博客:https://www.cnblogs.com/qicosmos/p/4551499.html
https://github.com/forhappy/Cplusplus-Concurrency-In-Practice
从多线程生产者与消费者例子开始(代码来源:https://blog.youkuaiyun.com/u011726005/article/details/78266668)
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <cassert>
namespace producer_consumer {
const int kRepositorySize = 10;
const int kDataCount = 100;
struct Repository {
int datas[kRepositorySize];
int read_position;
int write_position;
// 这个仓库是共享资源,也就是说消费者生产者读写同一个仓库,所以消费者生产者要用同一个互斥锁来实现线程安全。
// 所以这个互斥锁放在Repository这个类里面更合适。
std::mutex mutex;
// 用条件变量来实现线程同步。每个角色都拥有属于自己执行条件的变量。
// 对于生产者来说,不为满是执行条件,对于消费者来说不为空是执行条件。
std::condition_variable not_full_cv;
std::condition_variable not_empty_cv;
// 准备生产的数据。
std::vector<int> planed_datas;
Repository() {
memset(datas, 0, kRepositorySize);
consumed_count = 0;
produced_count = 0;
read_position = 0;
write_position = 0;
for (int i = 0; i < kDataCount; ++i) {
planed_datas.push_back(i + 1);
}
}
};
static void Producer(Repository& repository, int data) {
std::unique_lock<std::mutex> lock(repository.mutex);
// 循环队列有一个元素是不放值的,用于判断队列填满的条件。仓库满了则开始等待。
while ((repository.write_position + 1) % kRepositorySize == repository.read_position) {
repository.not_full_cv.wait(lock); // wait not full, 等待仓库不为满。
}
repository.datas[repository.write_position++] = data;
repository.write_position %= kRepositorySize;
std::cout << "+++Thread-" << std::this_thread::get_id() << ": produce: " << data << std::endl;
// 生产者生产了数据,那么仓库不为空,则可以通知消费者去消费。
repository.not_empty_cv.notify_all();
}
static void Consumer(Repository& repository) {
std::unique_lock<std::mutex> lock(repository.mutex);
// 循环队列,队列为空的条件。仓库为空则开始等待。
while (repository.read_position == repository.write_position) {
repository.not_empty_cv.wait(lock); // wait not empty, 等待仓库不为空。
}
int data = repository.datas[repository.read_position++];
repository.read_position %= kRepositorySize;
std::cout << "---Thread-" << std::this_thread::get_id() << ": consume: " << data << std::endl;
// 消费者数据,那么仓库不为满,则可以通知生产者去生产。
repository.not_full_cv.notify_all();
}
...
} // namespace producer_consumer
程序理解
语法1:互斥量
互斥量是一种线程同步的手段,用来保护多线程同时访问的共享数据。C++11中提供了4中语义的互斥量(mutex):
std::mutex:独占的互斥量,不能递归使用。
std::timed_mutex:带超时的独占互斥量,不能递归使用。
std::recursive_mutex:递归互斥量,不带超时功能。
std::recursive_timed_mutex:带超时的递归互斥量。
上述程序中使用了第一种,这些互斥量的用法相似,通过lock()方法阻塞线程,直到获得互斥量的所有权为止。线程获得互斥量并完成任务之后,就必须使用unlock()来解除对互斥量的占用。try_lock()尝试锁定互斥量,若成功则返回true,若失败则返回false,它是非阻塞的。
程序中使用的是unique_lock,关于unique_lock、lock_guard两者的区别:前者可以自由地释放mutex,lock_guard只能在析构时才释放mutex。
语法2:条件变量
在程序中的Producer函数中,while循环的作用是存储数据的仓库如果满了,则释放前面定义在数据仓库结构体中的mutex,将条件变量not_full_cv代表的生产者线程设置为waiting状态,一直等待;同时通过条件变量not_empty_cv通知其代表的消费者线程提取数据;Consumer函数与之相反。
所以总结条件变量使用过程:1)拥有条件变量的线程获取互斥量;2)循环检查某个条件,如果条件不满足,则阻塞直到条件满足;如果条件满足,则向下执行;3)某个线程满足条件执行完之后调用nontify_one或notify_all唤醒一个或者所有的等待线程。
(再补充点条件变量的基本概念,都是祁宇这本书里讲到的)条件变量是C++11提供的另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来用。C++11提供了两种条件变量:1)condition_variable,配合unique_lock<mutex>进行wait操作。2)condition_variable_any,和任意带有lock、unlock语义的mutex搭配使用,比较灵活,但效率比condition_variable差一些。应该根据具体应用场景来选择条件变量。
自己写的一个具体示例
示例说明:一个线程用于持续监测指定文件夹下是否有文件加入,如能够监测到新添加的图片,并将其添加到队列中;另一个线程用于对添加进队列的图片进行一些图像操作,这里以sift特征提取为例。
根据上面的线程知识,借鉴半同步半异步线程池的同步队列,实现如下:
定义一个头文件shili.h
#ifndef SHILI_H_
#define SHILI_H_
#pragma once
#include "stdafx.h"
#include <thread>
#include <mutex>
#include <chrono>
#include <iostream>
#include <queue>
#include <condition_variable>
#include <Windows.h>
#include <functional>
#include <SiftGPU.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/features2d/features2d.hpp"
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include "GL/glew.h"
#include <vector>
using namespace std;
class sfm
{
public:
sfm();
~sfm();
int m = 0;
int num[500] = { 0 };
vector<vector<float>> descriptors_for_all;
vector<vector<SiftGPU::SiftKeypoint>> keys_for_all;
private:
void file_monitor();
bool features_extraction_matching();
bool isDataQueueEmpty();
bool isDataQueueNotEmpty();
void pushDataToQueue(string &Data);
bool popDataFromQueue(string &Data);
std::mutex m_Mutex;
std::mutex m_Mutex4Queue;
std::queue<string> m_DataQueue;
std::condition_variable m_ConVar;
thread filemonitor;
thread featuresextractionmatching;
};
#endif
对应的源文件shili.cpp如下:
#include "stdafx.h"
#include "sfm.h"
using namespace std;
sfm::sfm()
{
filemonitor = thread(bind(&sfm::file_monitor,this));
featuresextractionmatching = thread(bind(&sfm::features_extraction_matching, this));
}
sfm::~sfm()
{
filemonitor.join();
featuresextractionmatching.join();
}
char* WideCharToMultiByte(LPCTSTR widestr)
{
int num = WideCharToMultiByte(CP_OEMCP, NULL, widestr, -1, NULL, 0, NULL, FALSE);
char *pchar = new char[num];
WideCharToMultiByte(CP_OEMCP, NULL, widestr, -1, pchar, num, NULL, FALSE);
return pchar;
}
bool IsDirectory(const LPTSTR & strPath)
{
DWORD dwAttrib = GetFileAttributes(strPath);
return static_cast<bool>((dwAttrib != 0xffffffff
&& (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)));
}
void sfm::file_monitor()
{
std::cout << "file_monitor_thread:begin" << std::endl;
HANDLE hDir;
BYTE* pBuffer = (LPBYTE)new CHAR[4096];
DWORD dwBufferSize;
LPTSTR lpPath = _T("指定文件夹路径");
WCHAR szFileName[MAX_PATH];
char* szFilePath;
PFILE_NOTIFY_INFORMATION pNotify = (PFILE_NOTIFY_INFORMATION)pBuffer;
hDir = CreateFile(lpPath, FILE_LIST_DIRECTORY,
FILE_SHARE_READ |
FILE_SHARE_WRITE |
FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_OVERLAPPED, NULL);
if (hDir == INVALID_HANDLE_VALUE)
{
printf("INVALID_HANDLE_VALUE");
}
string image_names;
while (true)
{
if (ReadDirectoryChangesW(hDir,
pBuffer,
4096,
TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
&dwBufferSize,
NULL,
NULL))
{
memset(szFileName, 0, MAX_PATH);
memcpy(szFileName, lpPath, _tcslen(lpPath) * sizeof(WCHAR));
memcpy(szFileName + _tcslen(lpPath), pNotify->FileName, pNotify->FileNameLength);
szFilePath = WideCharToMultiByte(szFileName);
switch (pNotify->Action)
{
case FILE_ACTION_ADDED:
if (IsDirectory(szFileName))
{
printf("Directory %s added.\n", szFilePath);
}
else
{
printf("File %s added.\n", szFilePath);
image_names_vector.emplace_back(szFilePath);
image_names = szFilePath;
std::unique_lock<std::mutex> lockTmp(m_Mutex);
pushDataToQueue(image_names);
m_ConVar.notify_all();
lockTmp.unlock();
}
break;
default:
break;
}
}
}
}
bool sfm::features_extraction_matching()
{
std::cout << "features_extraction_matching_thread:begin" << std::endl;
SiftGPU* sift = new SiftGPU(1);
double T = 20;
char numStr[5] = "700";
char*ftNum = NULL;
BOOL bHardMatchingImage = FALSE;
if (ftNum) strcpy(numStr, ftNum);
if (bHardMatchingImage)
{
sprintf(numStr, "2000");
T = 30;
}
const char * argv[] = { "-fo", "-1","-loweo", "-tc", numStr,"-cuda", "0" };
int argc = sizeof(argv) / sizeof(char*);
sift->ParseParam(argc, argv);
if (sift->CreateContextGL() != SiftGPU::SIFTGPU_FULL_SUPPORTED) return FALSE;
candidateimg_names.resize(500);
ID.resize(500);
vector<float> descriptors(1);
vector<SiftGPU::SiftKeypoint> keys(1);
SiftMatchGPU* matcher = new SiftMatchGPU(num[m]);
matcher->VerifyContextGL();
matcher->SetLanguage(3);
while (true)
{
std::unique_lock<std::mutex> lockTmp_1(m_Mutex);
while (isDataQueueEmpty())
m_ConVar.wait(lockTmp_1);
lockTmp_1.unlock();
string image_names1;
popDataFromQueue(image_names1);
cv::Mat image = cv::imread(image_names1);
int width = image.cols;
int height = image.rows;
sift->RunSIFT(width, height, image.data, GL_RGB, GL_UNSIGNED_BYTE);
num[m] = sift->GetFeatureNum();
keys.resize(num[m]);
descriptors.resize(128 * num[m]);
sift->GetFeatureVector(&keys[0], &descriptors[0]);
descriptors_for_all.push_back(descriptors);
cout << num[m] << endl;
m++;
}
}
bool sfm::isDataQueueEmpty()
{
std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue);
return m_DataQueue.empty();
}
bool sfm::isDataQueueNotEmpty()
{
std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue);
return !m_DataQueue.empty();
}
void sfm::pushDataToQueue(string &Data)
{
std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue);
m_DataQueue.push(Data);
}
bool sfm::popDataFromQueue(string &Data)
{
std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue);
if (m_DataQueue.size())
{
Data = m_DataQueue.front();
m_DataQueue.pop();
return true;
}
else
return false;
}
运行结果:
先输出监测到添加到指定文件夹中的图片名,然后对其进行sift特征提取,并输出提取的特征点数量
最后附上github链接,包括了头文件和相关库文件