C++11多线程编程系列-相关库函数使用

一、C++11 新标准中引入了多个头文件来支持多线程编程,分别是<atomic> ,<thread>,<mutex>,<condition_variable><future>

<atomic>:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操做的函数。
<thread>:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
<mutex>:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其余的类型和函数
<condition_variable>:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any
<future>:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。html

1、 std::thread

a. C++ 11中建立线程很是简单,使用std::thread类就能够,thread类定义于thread头文件,构造thread对象时传入一个可调用对象做为参数(若是可调用对象有参数,把参数同时传入),这样构造完成后,新的线程立刻被建立,同时执行该可调用对象ios

b. 用std::thread默认的构造函数构造的对象不关联任何线程;判断一个thread对象是否关联某个线程,使用joinable()接口,若是返回true,代表该对象关联着某个线程(即便该线程已经执行结束);算法

c. "joinable"的对象析构前,必须调用join()接口等待线程结束,或者调用detach()接口解除与线程的关联,不然会抛异常编程

d. 正在执行的线程从关联的对象detach后会自主执行直至结束,对应的对象变成不关联任何线程的对象,joinable()将返回false;promise

e. std::thread没有拷贝构造函数和拷贝赋值操做符,所以不支持复制操做(可是能够move),也就是说,没有两个 std::thread对象会表示同一执行线程;安全

f. 容易知道,以下几种状况下,std::thread对象是不关联任何线程的(对这种对象调用join或detach接口会抛异常):数据结构

默认构造的thread对象;多线程

被移动后的thread对象;并发

detach 或 join 后的thread对象;
g. 线程函数不只支持普通函数,还能够是类的成员函数lambda表达式app

h. 线程不像进程,一个进程中的线程之间是没有父子之分的,都是平级关系。即线程都是同样的, 退出了一个不会影响另一个。可是所谓的”主线程”main,其入口代码是相似这样的方式调用main的:exit(main(…))。main执行完以后, 会调用exit()。exit() 会让整个进程over终止,那全部线程天然都会退出。

即:若是进程中的任一线程调用了exit,_Exit或者_exit,那么整个进程就会终止。

i. move操做是将一个进程转移给另外一个进程,注意进程只能被转移不能被复制。也能够用swap交换两个线程。

 这个程序建立了两个线程,分别对变量num进行了10000次++操做,因为两个线程同时运行,++num也没有加锁保护,因此最后的输出结果在10000到20000之间,有必定随机性,也证实了++num不是原子操做

2、std::mutex   (轻松实现互斥)

常作多线程编程的人必定对mutex(互斥)很是熟悉,C++ 11固然也支持mutex,经过mutex能够方便的对临界区域加锁,std::mutex类定义于mutex头文件,是用于保护共享数据避免从多个线程同时访问的同步原语。它提供了lock,try_lock,unlock等几个接口,功能以下:

  1. 调用方线程从成功调用lock()或try_lock()开始,到unlock()为止占有mutex对象;
  2. 线程占有mutex时,全部其余线程若试图要求mutex的全部权,则将阻塞(对于 lock 的调用)或收到false返回值(对于 try_lock )
  3. 调用方线程在调用 lock 或 try_lock 前必须不占有mutex。
  4. mutex和thread同样,不可复制(拷贝构造函数和拷贝赋值操做符都被删除),并且,mutex也不可移动

用mutex改写上面的例子,达到两个线程不会同时++num的目的,改写以下:

 

 

 通过mutex对++语句的保护,使同一时刻,只可能有一个线程对num变量进行++操做,所以,这段程序的输出必然是20000。

注意:

a.操做系统提供mutex能够设置属性,C++11根据mutext的属性提供四种互斥量,分别是

std::mutex,最经常使用,广泛的互斥量(默认属性), 
std::recursive_mutex ,容许同一线程使用recursive_mutext屡次加锁,而后使用相同次数的解锁操做解锁。mutex屡次加锁会形成死锁
std::timed_mutex,在mutex上增长了时间的属性。增长了两个成员函数try_lock_for(),try_lock_until(),分别接收一个时间范围再给定的时间内若是互斥量被锁主了,线程阻塞,超过期间,返回false
std::recursive_timed_mutex,增长递归和时间属性

b.mutex成员函数加锁解锁

lock(),互斥量加锁,若是互斥量已被加锁,线程阻塞
bool try_lock(),尝试加锁,若是互斥量未被加锁,则执行加锁操做,返回true;若是互斥量已被加锁,返回false,线程不阻塞
void unlock(),解锁互斥量
c. mutex RAII式的加锁解锁

std::lock_guard,管理mutex的类。对象构建时传入mutex,会自动对mutex加入,直到离开类的做用域,析构时完成解锁。RAII式的栈对象能保证在异常情形下mutex能够在lock_guard对象析构被解锁
std::unique_lock 与 lock_guard功能相似,可是比lock_guard的功能更强大。好比std::unique_lock维护了互斥量的状态,可经过bool owns_lock()访问,当locked时返回true,不然返回false

3、std::lock_guard   (有做用域的mutex,让程序更稳定,防止死锁)

很容易想到,mutex的lock和unlock必须成对调用,lock以后忘记调用unlock将是很是严重的错误,再次lock时会形成死锁(其余线程就永远没法获得锁)。有时候一段程序中会有各类出口,如return,continue,break等等语句,在每一个出口前记得unlock已经加锁的mutex是有必定负担的,而假如程序段中有抛异常的状况,就更为隐蔽棘手,C++ 11提供了更好的解决方案,RAII。
类模板std::lock_guard是mutex封装器,经过便利的RAII机制在其做用域内占有mutex。

建立lock_guard对象时,它试图接收给定mutex的全部权。当程序流程离开建立lock_guard对象的做用域时,lock_guard对象被自动销毁并释放mutex,lock_guard类也是不可复制的。

通常,须要加锁的代码段,咱们用{}括起来造成一个做用域,括号的开端建立lock_guard对象,把mutex对象做为参数传入lock_guard的构造函数便可,好比上面的例子加锁的部分,咱们能够改写以下:

 

 

 进入做用域,临时对象guard建立,获取mutex控制权(构造函数里调用了mutex的lock接口),离开做用域,临时对象guard销毁,释放了mutex(析构函数里调用了unlock接口),这是对mutex的更为安全的操做方式(对异常致使的执行路径改变也有效),你们在实践中应该多多使用;

4、std::unique_guard 

std::lock_guard限制得太死了,只有构造和析构函数,无法经过它的成员函数加锁和解锁。为此,C++11提供了灵活的std:unique_lock模板类。std::unique_lock提供lock和unlock函数,所以能够在适当的时候加解锁。这样能够下降锁的粒度。默认状况下,std::unique_lock的构造函数会对mutex进行加锁,在析构的时候会对mutex进行解锁

#include<mutex>
std::unique_lock<std::mutex> ul(g_mutex);//构造函数进行上锁
......
ul.unlock();//解锁,下降锁的粒度
......
ul.lock();
......
//析构函数会进行解锁
std::unique_lock<std::mutex> ul1(m_mutex, std::defer_lock); //延迟上锁

  std::unique_lock<std::mutex> ul1(m_mutex, std::adopt_lock);//已经上锁

延迟上锁是指在构造函数里面不须要给它上锁已经上锁是表示在构造以前已经上锁了。  

std::lock_guard也是支持std::adopt_lock的,但不支持std::defer_lock,估计是由于std::lock_guard内部变量记录锁的状态,它只知道在构造函数加锁(或者由adopt_lock指明无需加锁),在析构函数解锁。

对于使用了std::defer_lock的std::unique_lock,之后手动加锁时要经过std::unique_lock类的lock()函数,而不用std::mutex的lock()函数由于std::unique_lock须要记录mutex的加锁状况

C++11提供了一个模板函数std::lock()使得很容易原子地多个锁进行加锁。std::lock函数只要求参数有lock操做便可,也就是说能够传一个std::mutex或者std::unique_lock变量给std::lock。std::lock_guard变量则不行,由于其没有lock()函数

template <class Mutex1, class Mutex2, class... Mutexes>
void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
std::lock(ul1, ul2);//同时对多个锁上锁

5、std::unique_lock与std::lock_guard区别

C++多线程编程中一般会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时形成资源争抢致使程序出现未定义的行为。一般的作法是在修改共享数据成员的时候进行加锁--mutex。在使用锁的时候一般是在对共享数据进行修改以前进行lock操做,在写完以后再进行unlock操做,常常会出现因为疏忽致使因为lock以后在离开共享成员操做区域时忘记unlock,致使死锁。

针对以上的问题,C++11中引入了std::unique_lock与std::lock_guard两种数据结构。经过对lock和unlock进行一次薄的封装,实现自动unlock的功能。

std::mutex mut;  
  
void insert_data()  
{  
       std::lock_guard<std::mutex> lk(mut);  
       queue.push_back(data);  
}  
  
void process_data()  
{  
       std::unqiue_lock<std::mutex> lk(mut);  
       queue.pop();  
}

std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,可是std::unique_lock要比std::lock_guard更灵活,可是更灵活的代价是占用空间相对更大一点且相对更慢一点。 

std::unique_lock 的构造函数的数目相对来讲比 std::lock_guard ,其中一方面也是由于 std::unique_lock 更加灵活,从而在构造 std::unique_lock 对象时能够接受额外的参数。总地来讲,std::unique_lock 构造函数以下:

default (1) unique_lock() noexcept;
locking (2) explicit unique_lock(mutex_type& m);
try-locking (3) unique_lock(mutex_type& m, try_to_lock_t tag);
deferred (4) unique_lock(mutex_type& m, defer_lock_t tag) noexcept;
adopting (5) unique_lock(mutex_type& m, adopt_lock_t tag);
locking for (6)


template <class Rep, class Period>

unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time);

locking until (7)


template <class Clock, class Duration>

unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);

copy [deleted] (8) unique_lock(const unique_lock&) = delete;
move (9) unique_lock(unique_lock&& x);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

下面咱们来分别介绍以上各个构造函数:

(1) 默认构造函数

新建立的 unique_lock 对象无论理任何 Mutex 对象。

(2) locking 初始化

新建立的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,若是此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞

(3) try-locking 初始化

新建立的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.try_lock() 对 Mutex 对象进行上锁,但若是上锁不成功,并不会阻塞当前线程。

(4) deferred 初始化

新建立的 unique_lock 对象管理 Mutex 对象 m,可是在初始化的时候并不锁住 Mutex 对象。 m 应该是一个没有当前线程锁住的 Mutex 对象。

(5) adopting 初始化

新建立的 unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(而且当前新建立的 unique_lock 对象拥有对锁(Lock)的全部权)。

(6) locking 一段时间(duration)

新建立的 unique_lock 对象管理 Mutex 对象 m,并试图经过调用 m.try_lock_for(rel_time)锁住 Mutex 对象一段时间(rel_time)。

(7) locking 直到某个时间点(time point)

新建立的 unique_lock 对象管理 Mutex 对象m,并试图经过调用 m.try_lock_until(abs_time)在某个时间点(abs_time)以前锁住 Mutex 对象。

(8) 拷贝构造 [被禁用]

unique_lock 对象不能被拷贝构造。

(9) 移动(move)构造

新建立的 unique_lock 对象得到了由 x 所管理的 Mutex 对象的全部权(包括当前 Mutex 的状态)。调用 move 构造以后, x 对象如同经过默认构造函数所建立的,就再也不管理任何 Mutex 对象了。

综上所述,由 (2) 和 (5) 建立的 unique_lock 对象一般拥有 Mutex 对象的锁。而经过 (1) 和 (4) 建立的则不会拥有锁。经过 (3),(6) 和 (7) 建立的 unique_lock 对象,则在 lock 成功时得到锁。

6、condition_variable

条件变量的详细介绍见https://www.cnblogs.com/GuoXinxin/p/11675053.html

C++里面使用条件变量实现和信号量相同的功能。下面代码是一个经典的生产者消费者模型:

#include<thread>
#include<iostream>
#include<mutex>
#include<list>
#include<condition_variable>

std::mutex g_mutex;
std::condition_variable cond;

std::list<int> alist;

void threadFun1()
{
    std::unique_lock<std::mutex> ul(g_mutex);
    while (alist.empty())
    {
        cond.wait(ul);
    }

    std::cout << "threadFun1 get the value : " << alist.front() << std::endl;
    alist.pop_front();
}

void threadFun2()
{
    std::lock_guard<std::mutex> lg(g_mutex);
    alist.push_back(13);

    cond.notify_one();
}

int main()
{
    std::thread th1(threadFun1);
    std::thread th2(threadFun2);

    th1.join();
    th2.join();

    return 0;
}

上面例子之因此用一个while循环而不是if,是由于存在虚假唤醒情景。当notify激活了多个线程以后,若是某个线程率先拿到锁将数据取空,其余线程应该再次检查一下数据是否为空。  

std::condition_variable 提供了两种 wait() 函数。当前线程调用 wait() 后将被阻塞(此时当前线程应该得到了锁(mutex),不妨设得到锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程
在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其余被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程得到通知(notified,一般是另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数也是自动调用 lck.lock(),使得 lck 的状态和 wait 函数被调用时相同

7、future

目的是为了得到线程函数的返回值,若是使用join的方法,主线程等待次线程结束后,再去读取全局变量便可。可是join是等待次线程结束,而结束有不少种缘由,好比正常结束和抛异常提早终止。对于后者,并不能保证join返回后,读取全局变量获得的就是所要的值。

 

#include<thread>
#include<iostream>
#include<mutex>
#include<vector>
#include<future>
#include<numeric>

void threadFun(const std::vector<int> &big_vec, std::promise<double> prom)
{
    double sum = std::accumulate(big_vec.begin(), big_vec.end(), 0.0);

    double avg = 0;
    if (!big_vec.empty())
        avg = sum / big_vec.size();

    prom.set_value(avg);
}

int main()
{
    std::promise<double> prom;
    std::future<double> fu = prom.get_future();

    std::vector<int> vec{ 1, 2, 3, 4, 5, 6 };
    //以右值引用的方式进行传递,本线程中的prom对象转移给了子线程,保证主线程不会一直阻塞。
    std::thread th(threadFun, std::ref(vec), std::move(prom));
    th.detach();

    double avg = fu.get();//阻塞一直到次线程调用set_value

    std::cout << "avg = " << avg << std::endl;

    return 0;
}

若是在析构std::promise变量时,还没对std::pormise变量进行设置,那么析构函数就会为其关联的std::future存储一个std::future_error异常。此时,std::future的get()函数会抛出一个std::futre_error异常。

std::future是一次性的。std::promise只能调用一次get_future,std::future也只能调用一次get()。 若是想在多个线程中共享一个std::promise的设置值,可使用std::shared_future。

有了std::packaged_task,线程函数就能够直接返回一个值。这样显得更加天然。从下面例子也能够看到,std::packaged_task并不是必定要做为std::thread的参数,它彻底能够在主线程中调用。

#include <iostream>
#include <cmath>
#include <thread>
#include <future>
#include <functional>

// unique function to avoid disambiguating the std::pow overload set
int f(int x, int y) { return std::pow(x,y); }

void task_lambda()
{
    std::packaged_task<int(int,int)> task([](int a, int b) {
        return std::pow(a, b); 
    });
    std::future<int> result = task.get_future();

    task(2, 9);

    std::cout << "task_lambda:\t" << result.get() << '\n';
}

void task_bind()
{
    std::packaged_task<int()> task(std::bind(f, 2, 11));
    std::future<int> result = task.get_future();

    task();

    std::cout << "task_bind:\t" << result.get() << '\n';
}

void task_thread()
{
    std::packaged_task<int(int,int)> task(f);
    std::future<int> result = task.get_future();

    std::thread task_td(std::move(task), 2, 10);
    task_td.join();

    std::cout << "task_thread:\t" << result.get() << '\n';
}

int main()
{
    task_lambda();
    task_bind();
    task_thread();
}

再用async进行一层封装:

#include<thread>
#include<iostream>
#include<vector>
#include<future>
#include<numeric>

double calcAvg(const std::vector<int> &vec)
{
    double sum = std::accumulate(vec.begin(), vec.end(), 0.0);
    double avg = 0;
    if (!vec.empty())
        avg = sum / vec.size();

    return avg;
}


int main()
{
    std::vector<int> vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    std::future<double> fu = std::async(calcAvg, std::ref(vec));

    double avg = fu.get();
    std::cout << "avg = " << avg << std::endl;

    return 0;
}

 8、atomic

我的理解,原子操做在性能上优与互斥量,当对某一种数据类型执行原子操做时,它会更多地使用物理机器上提供的原子操做,避免线程阻塞。若是不能够的话,可能内部会用自旋锁来解决同步问题。(原子操做,要么该线程执行完该操做,要么该操做都不执行)

最基本的是std::atomic_flag ,不过使用更多的是std::atomic,这还针对整型和指针作了模板特化。

在使用atomic时,会涉及到内存模型的概念。顺序一致性模型不只在共享存储系统上适用,在多处理器和多线程环境下也一样适用。而在多处理器和多线程环境下理解顺序一致性包括两个方面,(1). 从多个线程平行角度来看,程序最终的执行结果至关于多个线程某种交织执行的结果,(2)从单个线程内部执行顺序来看,该线程中的指令是按照程序事先已规定的顺序执行的(即不考虑运行时 CPU 乱序执行和 Memory Reorder)。

咱们在运行咱们的代码时,首先会通过编译器优化(可能会生成打乱顺序的汇编语言),CPU也可能会乱序执行指令以实现优化。内存模型对编译器和 CPU 做出必定的约束才能合理正确地优化你的程序。

9、自旋锁

互斥锁得不到锁时,线程会进入休眠,这类同步机制都有一个共性就是 一旦资源被占用都会产生任务切换,任务切换涉及不少东西的(保存原来的上下文,按调度算法选择新的任务,恢复新任务的上下文,还有就是要修改cr3寄存器会致使cache失效)这些都是须要大量时间的,所以用互斥之类来同步一旦涉及到阻塞代价是十分昂贵的。

一个互斥锁来控制2行代码的原子操做,这个时候一个CPU正在执行这个代码,另外一个CPU也要进入, 另外一个CPU就会产生任务切换。为了短短的两行代码 就进行任务切换执行大量的代码,对系统性能不利,另外一个CPU还不如直接有条件的死循环,等待那个CPU把那两行代码执行完。

当锁被其余线程占有时,获取锁的线程便会进入自旋,不断检测自旋锁的状态。一旦自旋锁被释放,线程便结束自旋,获得自旋锁的线程即可以执行临界区的代码。对于临界区的代码必须短小,不然其余线程会一直受到阻塞,这也是要求锁的持有时间尽可能短的缘由!

10、读写锁

读写锁和互斥量(互斥锁)很相似,是另外一种线程同步机制,但不属于POSIX标准,能够用来同步同一进程中的各个线程。固然若是一个读写锁存放在多个进程共享的某个内存区中,那么还能够用来进行进程间的同步,

和互斥量不一样的是:互斥量会把试图进入已保护的临界区的线程都阻塞;然而读写锁会视当前进入临界区的线程和请求进入临界区的线程的属性来判断是否容许线程进入。

相对互斥量只有加锁和不加锁两种状态,读写锁有三种状态:读模式下的加锁,写模式下的加锁,不加锁。

读写锁的使用规则:

只要没有写模式下的加锁,任意线程均可以进行读模式下的加锁;
只有读写锁处于不加锁状态时,才能进行写模式下的加锁;
读写锁也称为共享-独占(shared-exclusive)锁,当读写锁以读模式加锁时,它是以共享模式锁住,当以写模式加锁时,它是以独占模式锁住。读写锁很是适合读数据的频率远大于写数据的频率从的应用中。这样能够在任什么时候刻运行多个读线程并发的执行,给程序带来了更高的并发度。

11、线程同步

std::mutex mtx_syn;
std::condition_variable cv_syn;
std::condition_variable cv_syn_1;
bool ready = false;
void threadA(int id) {
	while (1)
	{
		std::unique_lock<std::mutex> lck(mtx_syn);
		while (!ready) cv_syn.wait(lck);
		// ...
		std::cout << "thread " << id << '\n';
		Sleep(500);
		cv_syn.notify_all();   //cpu 轮询执行 全部被唤醒的线程。
		cv_syn.wait(lck);
	}
	
}
void threadB(int id) {
	while (1)
	{
//新建立的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,若是此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞
		std::unique_lock<std::mutex> lck(mtx_syn);
		while (!ready) cv_syn.wait(lck);
		// ...
		std::cout << "thread " << id << '\n';
		Sleep(500);
		cv_syn.notify_all();
		cv_syn.wait(lck);
	}
}
 
 
void threadC(int id) {
	while (1)
	{
		std::unique_lock<std::mutex> lck(mtx_syn);
		while (!ready) cv_syn_1.wait(lck);
		// ...
		std::cout << "thread " << id << '\n';
		Sleep(500);
		cv_syn_1.notify_all();
		cv_syn_1.wait(lck);
	}
}
 
 
void go()
{
	std::unique_lock<std::mutex> lck(mtx_syn);
	ready = true;
	cv_syn.notify_one();
}
//线程同步
	std::thread threads[5];
	// spawn 10 threads:
	//for (int i = 0; i<5; ++i)
	//	threads[i] = std::thread(print_id, i);
	threads[0] = std::thread(threadA, 0);
	threads[1] = std::thread(threadB, 1);
	threads[2] = std::thread(threadC, 2);  //该线程 与 0, 1 无关,不影响 0,1 线程的同步,由于用的不是一个 condition_variable
	std::cout << "2 threads ready to race...\n";
	go();                       // go!
 
	for (auto& th : threads) th.join();

  

12、thread 使用

#include <iostream>
 
#include <thread>
 
std::thread::id main_thread_id = std::this_thread::get_id();
 
void hello()  
{
    std::cout << "Hello Concurrent World\n";
    if (main_thread_id == std::this_thread::get_id())
        std::cout << "This is the main thread.\n";
    else
        std::cout << "This is not the main thread.\n";
}
 
void pause_thread(int n) {
    std::this_thread::sleep_for(std::chrono::seconds(n));
    std::cout << "pause of " << n << " seconds ended\n";
}
 
int main() {
    std::thread t(hello);
    std::cout << t.hardware_concurrency() << std::endl;//能够并发执行多少个(不许确)
    std::cout << "native_handle " << t.native_handle() << std::endl;//能够并发执行多少个(不许确)
    t.join();
    std::thread a(hello);
    a.detach();
    std::thread threads[5];                         // 默认构造线程
 
    std::cout << "Spawning 5 threads...\n";
    for (int i = 0; i < 5; ++i)
        threads[i] = std::thread(pause_thread, i + 1);   // move-assign threads
    std::cout << "Done spawning threads. Now waiting for them to join:\n";
    for (auto &thread : threads)
        thread.join();
    std::cout << "All threads joined!\n";
}

十3、多线程应用实例

#include <iostream>
#include <opencv2/opencv.hpp>
#include "../data300w/util/Util.h"
#include "cunpd.hpp"
#include <ctime>
#include <io.h>
#include <direct.h>  
#include <thread>
#include <condition_variable>
#include <mutex>
#include <Windows.h>
 
 
using namespace std;
using namespace cv;
using namespace glasssix;
 
 
#define ZOOM_ 1.0
#define SIZE 96
 
 
extern void  splitString(const string& s, vector<string>& v, const string& c);
 
 
template <class Type>
Type stringToNum(const string& str)
{
	istringstream iss(str);
	Type num;
	iss >> num;
	return num;
}
void writeHistoFile(std::string filePath, vector<int> & densi_data)
{
	if (filePath == "" || densi_data.size() == 0)
	{
		return;
	}
	ofstream in;
	in.open(filePath, ios::app);   //ios::trunc
	int length = densi_data.size();
	for (int i = 0; i < length; i++)
	{
		string dataline = to_string(densi_data[i]);
		in << dataline << "\n";
	}
	in.close();
}
 
 
float getHorizontal(LandMark &landmark)
{
	float tan_theta = (landmark.points[1].y - landmark.points[0].y) / (landmark.points[1].x - landmark.points[0].x);
	float theta = atan(tan_theta);
	return theta * 180 / 3.1415926;
}
void showHistogram(vector<float> & hor_data)
{
	int densi[60] = { 0 };
	int length = hor_data.size();
	for (int i = 0; i < length; i++)
	{
		if (floor((hor_data[i] + 30)) >= 0 && floor((hor_data[i] + 30)) < 60)
		{
			densi[(int)floor((hor_data[i] + 30))]++;
		}
 
 
		if (floor((hor_data[i] + 30)) < 0)
		{
			densi[0]++;
		}
		else if (floor((hor_data[i] + 30)) >= 60)
		{
			densi[60]++;
		}
	}
	string density_text = "D:\\UMD\\density_text.txt";
	vector<int>density_data(densi, densi + 60);
	writeHistoFile(density_text, density_data);
 
 
	Mat histImg;
	histImg.create(1000, 1600, CV_8UC3);
	histImg.setTo(0);
	int offset = 10;
	for (int i = 0; i < 60; i++)
	{
		double tmpCount = densi[i];
		rectangle(histImg, Point2f(offset + i * 25, 1000), Point2f(offset + i * 25, 1000 - tmpCount / 15.0), Scalar::all(255), -1);  //画出直方图  
		putText(histImg, to_string(i - 29), Point2f(offset + i * 25 + 3, 1000 - 3), 0.3, 0.3, Scalar(0, 0, 255));
		Point2f pt0;
		pt0.x = offset + i * 25;
		pt0.y = 1000 - densi[i] / 15.0;
 
 
		Point2f pt1;
		pt1.x = offset + (i + 1) * 25;
		pt1.y = 1000 - densi[i + 1] / 15.0;
		line(histImg, pt0, pt1, Scalar(255, 0, 0), 1); //链接直方图的顶点  
	}
	imshow("hist", histImg);
	waitKey(0);
}
void getDatahor(string file1, vector<float> & hor_data)
{
	int mark_num = 5;
	DataPrepareUtil dpu;
	vector<LandMark> data;
	dpu.readFileData(file1, data, mark_num);
	int length = data.size();
	for (int i = 0; i < length; i++)
	{
		float hor = getHorizontal(data[i]);
		hor_data.emplace_back(hor);
	}
}
 
 
void rotation(float theta, Mat &img, Mat &dst, Size img_size, LandMark &landmark, int mark_num)
{
	//rotation
	Mat mat = img;
	Point2f center(img_size.width / 2, img_size.height / 2);
	double angle = theta;
 
 
	Mat rot = getRotationMatrix2D(center, angle, 1);
	Rect bbox = RotatedRect(center, mat.size(), angle).boundingRect();
 
 
	cv::warpAffine(mat, dst, rot, bbox.size());
 
 
	for (int j = 0; j < mark_num; j++)
	{
		float theta = -3.1415926 / (180 / angle);
		float x1 = landmark.points[j].x - rot.at<double>(1, 2);
		float y1 = landmark.points[j].y - rot.at<double>(0, 2);
		landmark.points[j].x = x1 * cos(theta) - y1 * sin(theta);
		landmark.points[j].y = x1 * sin(theta) + y1 * cos(theta);
 
 
		//circle(dst, Point(x, y), 2, Scalar(255, 0, 0));
	}
	//cv::imshow("dst", dst);
	//cv::waitKey(0);
}
 
 
void augment_data(string img_path, string img_text, string result_path, string result_text)
{
	DataPrepareUtil dpu;
	int mark_num = 5;
	srand((unsigned)time(NULL));
 
 
	vector<LandMark> data;
	dpu.readFileData(img_text, data, mark_num);
 
 
	vector<LandMark> rotation_data;
	vector<float> hor_data;
	getDatahor(img_text, hor_data);
	int length = hor_data.size();
	for (int i = 0; i < length; i++)
	{
		if (hor_data[i] > 0 && hor_data[i] < 3)
		{
			Mat dst;
			Mat img = imread(img_path + data[i].fileName);
			LandMark landmark(data[i]);
			rotation(25, img, dst, Size(96, 96), landmark, mark_num);
			rotation_data.push_back(landmark);
		}
	}
 
 
}
 
 
bool getFaceRect(cunpd &pd, int model_id, Mat &dstImg, LandMark & landmark, Rect & rect)
{
	const int widths = dstImg.cols;
	const int heights = dstImg.rows;
	vector<FaceInfomation>  face = pd.detect(dstImg, model_id, 48);
	int length = face.size();
	if (length == 0)
	{
		cout << "not found face ." << endl;
	}
	for (int j = 0; j < length; j++)
	{
		if (face[j].score > 15)
		{
			rect = face[j].rect;
 
 
			if (landmark.points[0].x > rect.x && landmark.points[0].x < rect.x + rect.width
				&& landmark.points[0].y > rect.y && landmark.points[0].y < rect.y + rect.height
				&&landmark.points[12].x > rect.x && landmark.points[12].x < rect.x + rect.width
				&& landmark.points[12].y > rect.y && landmark.points[12].y < rect.y + rect.height
				&&landmark.points[16].x > rect.x && landmark.points[16].x < rect.x + rect.width
				&& landmark.points[16].y > rect.y && landmark.points[16].y < rect.y + rect.height
				&&landmark.points[20].x > rect.x && landmark.points[20].x < rect.x + rect.width
				&& landmark.points[20].y > rect.y && landmark.points[20].y < rect.y + rect.height
				&& (abs(landmark.points[7].y - landmark.points[17].y) > (rect.height / 6.0)))
			{
				int rect_w = rect.width;
				int rect_h = rect.height;
				rect.width = rect_w * ZOOM_;
				rect.height = rect_h * ZOOM_;
				rect.x = max(rect.x - (ZOOM_ - 1.0) * rect_w / 2.0, 0.0);
				rect.y = max(rect.y - (ZOOM_ - 1.0) * rect_h / 2.0, 0.0);
 
 
				if (rect.x + rect.width > widths)
				{
					rect.width = widths - rect.x;
				}
				if (rect.y + rect.height > heights)
				{
					rect.height = heights - rect.y;
				}
				return true;
			}
		}
	}
	return false;
 
 
}
 
 
void getoffsetRect(Rect & rect, vector<Rect> & all_rect, int cols, int rows, int max_offset)
{
	srand((unsigned)time(NULL));
	Rect rect0(rect), rect1(rect);
	int offsetx = rand() % max_offset + 1;
	int offsety = rand() % max_offset + 1;
 
 
	if (rect.x > offsetx && rect.y > offsety)
	{
		rect0.x = rect.x - offsetx;
		rect0.y = rect.y - offsety;
	}
 
 
	offsetx = rand() % max_offset + 1;
	offsety = rand() % max_offset + 1;
 
 
	if (rect.x + rect.width + offsetx < cols && rect.y + rect.height + offsety < rows)
	{
		rect1.x = rect.x + offsetx;
		rect1.y = rect.y + offsety;
	}
	all_rect.push_back(rect0);
	all_rect.push_back(rect1);
}
 
 
#define NEED_LANDMARK 5
#define CURRENT_LANDMARK 21
const int five_points[5] = { 7, 10, 14, 17, 19 };
string search_base = "H:\\UMD\\";
string search_dir_[] = { search_base + "umdfaces_batch1", search_base + "umdfaces_batch2", search_base + "umdfaces_batch3" };
 
 
string text_file[] = { search_base + "umdfaces_batch1\\umdfaces_batch1_ultraface.csv", search_base + "umdfaces_batch2\\umdfaces_batch2_ultraface.csv", search_base + "umdfaces_batch3\\umdfaces_batch3_ultraface.csv" };
string text_pre[] = { "batch1_aug_", "batch2_aug_", "batch3_aug_" };
string tail_[] = { ".jpg", ".jpg" , ".jpg" };
 
 
string base = search_base + "landmark_5\\augment_img\\";
string result_img = base + "result_img_" + to_string(SIZE) + "\\";
string result_txt = base + "landmark_" + to_string(SIZE) + "_5.txt";
const int theta_offset = 5;
const int theta_max = 20;
vector<LandMark> rotation_point;
int countNum = 0;
bool ready = false;
std::mutex mtx_syn;
std::condition_variable cv_syn;
void roll_yaw_pitch_data(LandMark result_mark, int temp, cunpd &pd, int model_id, DataPrepareUtil &dpu)
{
 
 
	float roll = result_mark.direct[2];
 
 
	string img_path = search_dir_[temp] + "\\" + result_mark.fileName;
	if (_access(img_path.c_str(), 0) == -1)
	{
		cout << "coun't found filename" << img_path << endl;
		return;
	}
	Mat img = imread(img_path);
	Mat dstImg; //dstImg.create(heights, widths, CV_8UC1);
	cvtColor(img, dstImg, CV_BGR2GRAY);
	//yaw 加强 pitch 加强
	for (int j = 0; j < 2; j++)
	{
		if (result_mark.direct[j] > -theta_offset && result_mark.direct[j] < theta_offset)
		{
			Rect rect;
			LandMark landmark(result_mark);
			bool success = getFaceRect(pd, model_id, dstImg, landmark, rect);
			if (success)
			{
				vector<Rect> all_rect;
				getoffsetRect(rect, all_rect, img.cols, img.rows, 4);
				for (int i = 0; i < 2; i++)
				{
					LandMark dst_landmark;
					//vector<string> filenames;
					//splitString(landmark.fileName, filenames, "/");
					//string filename = filenames[filenames.size()-1];
					
					std::unique_lock<std::mutex> lck(mtx_syn);
					dst_landmark.fileName = text_pre[temp] + to_string(countNum++) + ".png";
					lck.unlock();
					//cout << img.rows << " " << rotat_img.cols << " " << rect.x << " " << rect.y << " " << rect.width << " " << rect.height << endl;
 
 
					Mat roi_face = img(all_rect[i]);
					cv::resize(roi_face, roi_face, Size(SIZE, SIZE));
					//坐标转换
					for (int k = 0; k < 5; k++)
					{
						dst_landmark.visible[k] = landmark.visible[five_points[k]];
						dst_landmark.points[k].x = ((float)SIZE / all_rect[i].width) * (landmark.points[five_points[k]].x - all_rect[i].x);
						dst_landmark.points[k].y = ((float)SIZE / all_rect[i].height) * (landmark.points[five_points[k]].y - all_rect[i].y);
					}
					imwrite(result_img + dst_landmark.fileName, roi_face);
 
 
					std::unique_lock<std::mutex> lck1(mtx_syn);
					rotation_point.push_back(dst_landmark);
					lck1.unlock();
				}
			}
 
 
		}
	}
	// roll 加强
	if (roll > -theta_offset && roll < theta_offset)
	{
		for (int i = -1; i < 2; i = i + 2)
		{
			Mat rotat_img;
			LandMark landmark(result_mark);
			int theta = (rand() % theta_max + theta_offset) * i;
			rotation(theta, img, rotat_img, Size(SIZE, SIZE), landmark, CURRENT_LANDMARK);
 
 
			Mat dstImg; //dstImg.create(heights, widths, CV_8UC1);
			cvtColor(rotat_img, dstImg, CV_BGR2GRAY);
 
 
			//for (int j = 0; j < CURRENT_LANDMARK; j++)
			//{
			//	circle(rotat_img, Point(landmark.points[j]), 2, Scalar(255, 0, 0));
			//}
			//imshow("img", rotat_img);
			//waitKey(0);
 
 
			LandMark dst_landmark;
 
 
			//vector<string> filenames;
			//splitString(landmark.fileName, filenames, "/");
			//string filename = filenames[filenames.size()-1];
			std::unique_lock<std::mutex> lck(mtx_syn);
			dst_landmark.fileName = text_pre[temp] + to_string(countNum++) + ".png";
			lck.unlock();
			Rect rect;
			bool success = getFaceRect(pd, model_id, dstImg, landmark, rect);
			if (success)
			{
				//cout << rotat_img.rows << " " << rotat_img.cols << " " << rect.x << " " << rect.y << " " << rect.width << " " << rect.height << endl;
				Mat roi_face = rotat_img(rect);
				cv::resize(roi_face, roi_face, Size(SIZE, SIZE));
				//坐标转换
				for (int k = 0; k < 5; k++)
				{
					dst_landmark.visible[k] = landmark.visible[five_points[k]];
					dst_landmark.points[k].x = ((float)SIZE / rect.width) * (landmark.points[five_points[k]].x - rect.x);
					dst_landmark.points[k].y = ((float)SIZE / rect.height) * (landmark.points[five_points[k]].y - rect.y);
				}
				imwrite(result_img + dst_landmark.fileName, roi_face);
 
 
				std::unique_lock<std::mutex> lck(mtx_syn);
				rotation_point.push_back(dst_landmark);
 
 
				if (rotation_point.size() > 500)
				{
					dpu.writePointVisibletoFile(result_txt, rotation_point, NEED_LANDMARK);
					rotation_point.clear();
				}
				if (countNum % 500 == 0)
				{
					cout << "prepare data:" << countNum << endl;
				}
				lck.unlock();
			}
		}
	}
 
 
 
 
 
 
}
 
 
vector<LandMark> result_point;   //注意 使用多线程 时共同处理的 变量用 全局变量。
void deal_thread(int temp, int model_id, DataPrepareUtil &dpu, cunpd &pd)
{
	while (true)
	{
		std::unique_lock<std::mutex> lck(mtx_syn);
		while (!ready) {
			cv_syn.wait(lck);
		}
		//
		auto itor = result_point.begin();
		auto itor2 = result_point.end();
		if (itor == itor2)
		{
			break;
		}
		LandMark landmark(result_point[0]);
		result_point.erase(itor);
//		cout << "landmark.fileName is:"<<landmark.fileName<< "thread id"<< this_thread::get_id()<< endl;
		lck.unlock();
 
 
		roll_yaw_pitch_data(landmark, temp, pd, model_id, dpu);
		
	}
 
 
}
 
 
void go()
{
	std::unique_lock<std::mutex> lck(mtx_syn);
	ready = true;
	cv_syn.notify_all();
}
 
 
 
int main()
{
 
	cunpd pd;
	int model_id = pd.AddNpdModel(0);
 
	/*string img_path = "D:\\UMD\\result_img_96\\";
	string result_path = "D:\\UMD\\arguement_data\\";
	string img_text = img_path + "shutter_96_5_train.txt";
	string result_text = result_path + "augment_96_5_train.txt";
	augment_data(img_path, img_text, result_path, result_text);*/
 
	string base_dir = base;
	if (_access(base_dir.c_str(), 0) == -1)
	{
		_mkdir(base_dir.c_str());
	}
	string dir = result_img;
	if (_access(dir.c_str(), 0) == -1)
	{
		_mkdir(dir.c_str());
	}
 
 
	srand((unsigned)time(NULL));
	DataPrepareUtil dpUtil;
 
	dpUtil.clearFileData(result_txt);
 
	long count = 0;
 
	vector<LandMark> rotation_point;
	for (int temp = 0; temp < 3; temp++)
	{
		long countNum = 0;
		//vector<LandMark> result_point;
 
 
		dpUtil.readFileData(text_file[temp], result_point, CURRENT_LANDMARK);
 
 
		std::thread threads[4];
		for (int i = 0; i < 4; i++)
		{
			threads[i] = std::thread(deal_thread, temp, model_id, dpUtil, pd);
			//threads[i] = std::thread(threadA, i, result_point, temp, model_id, dpUtil, pd);
		}
		cout << "temp start:" << temp << endl;
		go();
		for (auto &th : threads) {
			th.join();
		}
		cout << "temp end:" << temp << endl;
		
		}
		if (rotation_point.size() > 0)
		{
			dpUtil.writePointVisibletoFile(result_txt, rotation_point, NEED_LANDMARK);
			rotation_point.clear();
		}
		system("PAUSE");
		return 0;
}

  

 十4、Future使用

void test_thread() {
    //1.
    std::thread t(foo, "hello");
    t.join();
 
    //2.
    std::packaged_task<int(int)> task([](int a) {std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "my task" << std::endl; return a; });
    std::future<int> result = task.get_future();
    std::thread(std::move(task), 2).detach();
    std::cout << "Waiting...." << std::endl;
    //result.wait();
 
    //result.get会阻塞,直到对应线程完成
    std::cout << "Done  result is:" << result.get() << std::endl;
 
    //3.
    std::packaged_task<string(string)> task1(foo);
    std::future<string> result1 = task1.get_future();
    string str = "liu";
    std::thread(std::move(task1), str).detach();
    //result1.get会阻塞,直到对应线程完成
    std::cout << "task1:" << result1.get() << std::endl;
}

  

 

 

整理于:

https://blog.csdn.net/u011808673/article/details/80811998

https://blog.csdn.net/zhougb3/article/details/79538750

相关文章
相关标签/搜索