关于C++11多线程开发的理解和自己写的一个具体应用

参考:

书《深入应用C++11:代码优化与工程级应用》 祁宇

博客:https://www.cnblogs.com/qicosmos/p/4551499.html

          https://github.com/forhappy/Cplusplus-Concurrency-In-Practice

从多线程生产者与消费者例子开始(代码来源:https://blog.csdn.net/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链接,包括了头文件和相关库文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值