关于多线程,你需要了解的一些基础知识3

365 阅读3分钟

1.call_once的使用

在多线程编程中,有时某个任务只需要执行一次,此时可以用C++11中的std::call_once函数配合std::once_flag来实现。如果多个线程需要同时调用某个函数,std::call_once可以保证多个线程对该函数只调用一次。

#include<iostream>
#include<mutex>
#include<thread>
using namespace std;
once_flag init_flag;
 
void init()
{
	cout << "data has inited" << endl;
}
 
void fun()
{
	call_once(init_flag, init);
}
 
int main()
{
	thread t1(fun);
	thread t2(fun);
	t1.join();
	t2.join();
	system("pause");
	return 0;
}

2.shared_mutex

C++14提供了shared_mutex来解决读者-写者问题,也就是读写锁,和普通锁不一样,读写锁同时只能有一个写者或多个读者,但不能同时既有读者又有写者,读写锁的性能一般比普通锁要好。

shared_mutex g_mutex;
std::string g_str;

void readLoop()
{
	while (true) {
		this_thread::sleep_for(chrono::milliseconds(100));
		g_mutex.lock_shared();
		cout << g_str;
		g_mutex.unlock_shared();
	}
}

void writeLoop()
{
	int number = 0;
	while (true) {
		this_thread::sleep_for(chrono::milliseconds(100));
		g_mutex.lock();
		g_str = to_string(number++)+"\n";
		g_mutex.unlock();
	}
}

int main()
{
	thread(writeLoop).detach();
	thread(readLoop).detach();
	thread(readLoop).detach();
	system("pause");
}

3.多线程 利用条件变量实现线程安全的队列

背景:标准STL库的队列queue是线程不安全的。 利用条件变量(Condition variable)简单实现一个线程安全的队列

#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <thread>

template<typename T>
class threadsave_queue{
private:
  mutable std::mutex mut;//必须是mutable,因为empty是const方法,但是要锁mut,锁操作就是改变操作
  std::queue<T> data_queue;
  std::condition_variable data_cond;
public:
  threadsave_queue(){}
  threadsave_queue(threadsave_queue const& other){
    std::lock_guard<std::mutex> lk(other.mut);
    data_queue = other.data_queue();
  }
  void push(T new_value){
    std::lock_guard<std::mutex> lk(mut);
    data_queue.push(new_value);
    data_cond.notify_one();
  }
  void wait_and_pop(T& value){
    std::unique_lock<std::mutex> lk(mut);
    data_cond.wait(lk, [this]{return !data_queue.empty();});
    value = data_queue.front();
    data_queue.pop();
  }

  std::shared_ptr<T> wait_and_pop(){
    std::unique_lock<std::mutex> lk(mut);
    data_cond.wait(lk, [this]{return !data_queue.empty();});
    std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
    data_queue.pop();
    return res;
  }
  
  bool empty()const{
    std::lock_guard<std::mutex> lk(mut);
    return data_queue.empty();
  }
};

void make_data(threadsave_queue<int>& tq, int val){
  tq.push(val);
}
void get_data1(threadsave_queue<int>& tq, int& d1){
  tq.wait_and_pop(d1);
}
void get_data2(threadsave_queue<int>& tq, int& d1){
  auto at = tq.wait_and_pop();
  d1 = *at;
}
int main(){
  threadsave_queue<int> q1;
  int d1;
  std::thread t1(make_data, std::ref(q1), 10);
  std::thread t2(get_data1, std::ref(q1),std::ref(d1));
  t1.join();
  t2.join();
  std::cout << d1 << std::endl;
  std::thread t3(make_data, std::ref(q1), 20);
  std::thread t4(get_data2, std::ref(q1),std::ref(d1));
  t3.join();
  t4.join();
  std::cout << d1 << std::endl;
  q1.empty();
}

4.packaged_task使用介绍

packaged_task类模板也是定义于future头文件中,它包装任何可调用 (Callable) 目标,包括函数、 lambda 表达式、 bind 表达式或其他函数对象,使得能异步调用它,其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。简言之,将一个普通的可调用函数对象转换为异步执行的任务。

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <chrono>
#include <future>

using namespace std;
//普通函数
int Add(int x, int y)
{
    return x + y;
}


void task_lambda()
{
    //包装可调用目标时lambda
    packaged_task<int(int,int)> task([](int a, int b){ return a + b;});
    
    //仿函数形式,启动任务
    task(2, 10);
    
    //获取共享状态中的值,直到ready才能返回结果或者异常
    future<int> result = task.get_future();
    cout << "task_lambda :" << result.get() << "\n";
}

void task_thread()
{
    //包装普通函数
    std::packaged_task<int (int,int)> task(Add);
    future<int> result = task.get_future();
    //启动任务,非异步
    task(4,8);
    cout << "task_thread :" << result.get() << "\n";
        
    //重置共享状态
    task.reset();
    result = task.get_future();

    //通过线程启动任务,异步启动
    thread td(move(task), 2, 10);
    td.join();
    //获取执行结果
    cout << "task_thread :" << result.get() << "\n";
}

int main(int argc, char *argv[])
{
    task_lambda();
    task_thread();

    return 0;
}

5.多线程 等待一次性事件 std::promise用法

std::promise用来包装一个值将数据和future绑定起来,为获取线程函数的某个值获取便利,取值是间接通过promise内部提供的future来获取的。promise的主要目的是提供一个”Set”操作,和future的get()对应


#include<iostream>
#include<future>
#include<thread>
 
using namespace  std;
using namespace  std::this_thread;
using namespace  std::chrono;
 
//费时操作
void  work(promise<int>  &  prom)
{
	cout << "开始计算!" << endl;
	sleep_for(seconds(3));
 
	//promise设置结果值
	cout << "计算完成!" << endl;
	prom.set_value(123);//设置结果,future会get到
}
 
int  main()
{
	//定义一个promise
	promise<int>  prom;
 
	//future和promise搭配使用,类似于aynsc
	future<int>  result = prom.get_future();
 
	thread  t1(work , ref(prom));
	t1.detach();
	 
	int  sum = result.get();
	cout << "获取结果:" << sum << endl;
 
	system("pause");
	return 0;
}