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

1、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() 函数就声明在此头文件中。

一、 std::thread

a. C++ 11中创建线程非常简单,使用std::thread类就可以,thread类定义于thread头文件,构造thread对象时传入一个可调用对象作为参数(如果可调用对象有参数,把参数同时传入),这样构造完成后,新的线程马上被创建,同时执行该可调用对象

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

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

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

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

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

默认构造的thread对象;

被移动后的thread对象;

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

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

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

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

 这个程序创建了两个线程,分别对变量num进行了10000次++操作,由于两个线程同时运行,++num也没有加锁保护,所以最后的输出结果在10000到20000之间,有一定随机性,也证明了++num不是原子操作

二、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

三、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的更为安全的操作方式(对异常导致的执行路径改变也有效),大家在实践中应该多多使用;

四、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);//同时对多个锁上锁

五、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 成功时获得锁。

六、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 函数被调用时相同

七、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;
}

 八、atomic

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

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

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

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

九、自旋锁

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

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

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

十、读写锁

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

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

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

读写锁的使用规则:

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

十一、线程同步

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();

  

十二、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";
}

十三、多线程应用实例

#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;
}

  

 十四、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

 

posted @ 2019-10-14 21:14  GuoXinxin  阅读(1176)  评论(0编辑  收藏  举报