线程类Thread

  1. 常用的一个构造函数:explicit thread(_Callable&& __f, _Args&&... __args)
    1. f的类型为普通的类成员函数示例:
    class Test {
    public:
        void introduce(std::string name, int age) {
            std::cout << "name:" << name << ",age:" << age << std::endl;
        }
    };
    int main()
    {
        Test test;
        // 第二个参数是传递给普通成员函数的this指针的
        std::thread t(&Test::introduce, &test, "NrvCer", 23); 
        t.join();
        return 0;   
    }
    
    1. f的类型为静态的类成员函数示例:
    #include <iostream>
    #include <thread>
    #include <string>
    
    class Test {
    public:
        static void introduce(std::string name, int age) {
            std::cout << "name:" << name << ",age:" << age << std::endl;
        }
    };
    int main()
    {
        std::thread t(Test::introduce, "NrvCer", 23);
        t.join();
        return 0;   
    }
    
    1. f的类型为仿函数
    #include <iostream>
    #include <thread>
    #include <string>
    #include <functional>
    
    class Test {
    public:
        void introduce(std::string name, int age) {
            std::cout << "name:" << name << ",age:" << age << std::endl;
        }
    };
    int main()
    {
        Test test;
        std::thread t(std::bind(&Test::introduce, &test, "NrvCer", 23)); 
        t.join();
        return 0;   
    }
    
    1. f的类型为匿名函数:例如lambda表达式
    class Test {
    public:
        void introduce(std::string name, int age) {
            std::cout << "name:" << name << ",age:" << age << std::endl;
        }
    };
    int main()
    {
        Test test;
        std::thread t([&test]() {
            test.introduce("NrvCer", 23);
            });
        t.join();
        return 0;
    }
    
  2. 公共静态成员函数之get_id:获取线程id
  3. 公共成员函数之join:阻塞调用join函数的线程。一般在主线程中调用这个函数,阻塞主线程。当子线程任务处理完毕,join会清理子线程中的相关资源然后返回。然后调用join的主线程才能解除阻塞继续向下执行。
  4. detach:进行线程分离,分离主线程和创造出的子线程。在线程分离之后,主线程退出也会一并销毁创建出的所有子线程
  5. joinable:判断主线程和子线程是否处于关联状态。函数返回值为true,表示主线程和子线程之间有关联关系;返回值为false,表示主线程和子线程之间没有关联关系。一般先使用这个函数判断某个线程是否可以join。
  6. 静态函数hardware_concurrency:获取当前计算机的CPU核心数
// 12
std::cout << std::thread::hardware_concurrency() << std::endl;

线程的命名空间this_thread

c++11的线程的命名空间std::this_thread中提供了四个公共的成员函数。

  1. get_id:获取当前线程的线程id。
    1. 函数原型如下:
    thread::id get_id() noexcept;
    
    1. 函数使用示例:
    #include <thread>
    #include <iostream>
    
    void func() {
        std::cout << "子线程id:" << std::this_thread::get_id() << std::endl;
    }
    int main() {
        std::cout << "主线程id:" << std::this_thread::get_id() << std::endl;
        std::thread t(func);
        t.join();
        return 0;
    }
    
  2. sleep_for函数:调用这个函数的线程将从运行态变为阻塞态,在阻塞态下休眠一定的时长。
    1. 函数原型如下:
    template<typename _Rep, typename _Period>
    inline void
    sleep_for(const chrono::duration<_Rep, _Period>& __rtime)
    
    1. 函数使用示例:
    #include <thread>
    #include <iostream>
    
    void func() {
        for (int i = 0; i < 5; ++i) {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::cout << "子线程id:" << std::this_thread::get_id() << ",i = " << i << std::endl;
        }
    }
    
    int main() {
        std::thread t(func);
        t.join();
        return 0;
    }
    
  3. sleep_until函数:指定调用这个函数的线程阻塞到指定的时间点
    1. 函数原型如下:
    template<typename _Clock, typename _Duration>
    inline void
    sleep_until(const chrono::time_point<_Clock, _Duration>& __atime)
    
    1. 函数使用示例
    #include <thread>
    #include <iostream>
    
    void func() {
        for (int i = 0; i < 5; ++i) {
            auto now = std::chrono::system_clock::now();
            std::chrono::seconds sec(2);
            std::this_thread::sleep_until(now + sec);
            std::cout << "子线程id:" << std::this_thread::get_id() << ",i = " << i << std::endl;
        }
    }
    
    int main() {
        std::thread t(func);
        t.join();
        return 0;
    }
    
  4. yield函数:在线程中调用这个函数之后,处于运行态的线程会主动让出自己已经抢到的 CPU 时间片,最终变为就绪态。但是在下一个时间片中,这个处于就绪态的线程还是会继续争抢时间片的。
    1. 函数原型:
    inline void yield() noexcept
    
    1. 函数使用示例
    // 如果当前线程没有运算完毕就放弃当前线程占用的时间片
    while (!isDone()) {
        std::this_thread::yield();
    }
    

call_once函数

  1. call_once函数用来保证内部的回调函数只会被执行一次。
    1. 该函数原型如下:
    void call_once(once_flag& __once, _Callable&& __f, _Args&&... __args)
    函数参数:
        once:要保证这个对象能够被多个线程同时访问到
        f:回调函数,可以传递一个有名函数地址,也可以指定一个匿名函数
        args:作为实参传递给回调函数
    
    1. 示例
    #include <iostream>
    #include <thread>
    #include <string>
    #include <mutex>
    
    // 未经初始化的全局变量位于bss段,所有线程共享
    std::once_flag g_flag;
    
    void execute_output(std::string name, int age) {
        std::cout << "name:" << name << ",age:" << age << std::endl;
    }
    void func(std::string name, int age) {
        static int count = 0;
        // execute_output只被执行一次
        std::call_once(g_flag, execute_output, name, age);
        // func被执行两次
        std::cout << "func execute count:" << ++count << std::endl;
    }
    
    int main()
    {
        std::thread t1(func, "NrvCer", 23);
        std::thread t2(func, "NrvCer", 23);
        t1.join();
        t2.join();
    
        return 0;   
    }
    
  2. call_once的应用:使用懒汉模式创建单例模式的类
#include <iostream>
#include <mutex>    // for call_once
#include <thread>   // for class thread
std::once_flag g_flag;

class Singleton {
public:
    static Singleton* getInstance() {
        std::call_once(g_flag, [&]() {
            obj = new Singleton();
            std::cout << "initial success\n";
        });
        return obj;
    }
private:
    static Singleton* obj;
};

Singleton* Singleton::obj = nullptr;

void func() {
    std::cout << Singleton::getInstance() << std::endl;
}
int main() {
    // test
    std::thread t1(func);
    std::thread t2(func);
    
    t1.join();
    t2.join();

    return 0;
}

线程同步之互斥锁

c++11中一共提供了四种互斥锁。分别是:

  1. mutex:这是独占的互斥锁,不能递归使用
  2. timed_mutex:带超时的独占互斥锁,不能递归使用
  3. recursive_mutex:递归互斥锁,不带超时功能
  4. recursive_timed_mutex:带超时的递归互斥锁

补充:C++14中提供了带超时的可共享互斥锁shared_timed_mutexC++17中提供了共享的互斥锁shared_mutex。

1.std::mutex
  1. mutex是一种独占互斥锁,互斥锁对象的个数和共享资源的个数相等,它和线程的个数没有关系。
  2. std::mutex类中常用的成员函数:
    1. lock:用于给临界区加锁,并且只能有一个线程获得锁的所有权,它有阻塞线程的作用
    2. unlock:解除对临界区的锁定
    3. try_lock:尝试给临界区加锁。这个函数和lock的区别就是这个函数不会阻塞线程。
    4. 示例:两个线程数数,从1顺序数到20。如果需要交替数数则使用条件变量或者信号量
    #include <iostream>
    #include <thread>
    #include <mutex>
    
    int g_number = 0;
    std::mutex m;
    
    void count(int id) {
        for (int i = 0; i < 10; ++i) {
            m.lock();
            g_number++;
            std::cout << id << ":" << g_number << std::endl;
            // 需要保证unlock操作执行,否则导致其中一个子线程永远获取不到锁
            m.unlock();
            // 阻塞当前线程给其他线程机会
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }
    int main() {
    
        std::thread t1(count, 0);
        std::thread t2(count, 1);
    
        t1.join();
        t2.join();
    
        return 0;
    }
    
  3. std::lock_guard:使用这个类,可以简化使用互斥锁的lock和unlock方法。原理是使用explicit lock_guard(mutex_type& __m)构造函数构造lock_guard对象时,会自动锁定互斥量;当lock_guard对象退出作用域时,析构函数内部会自动解除对互斥量的锁定。
    1. 示例
    // 使用lock_guard对上述代码进行改进
    void count(int id) {
        for (int i = 0; i < 10; ++i) {
            // 使用作用域块控制互斥锁的锁粒度
            {
                std::lock_guard<std::mutex> lock(m);
                g_number++;
                std::cout << id << ":" << g_number << std::endl;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }
    
    1. lock_guard的优缺点:
      1. 缺点是:锁的粒度不能进行灵活控制,相比于lock方法和unlock方法,锁的粒度变大了。但是可以通过将lock_guard相关代码放在代码块中减少锁的粒度。
      2. 优点是:不用手动进行互斥锁的释放
  4. unique_lock:相比于lock_gaurd,使用这个类可以进一步减少锁的粒度,因为这个类提供了更多的成员函数
#include <iostream>
#include <mutex>
#include <thread>

using namespace std;
int global_var = 0;
mutex m1;

void func() {
    for (int i = 0; i < 1000000; i++) {
        unique_lock<mutex> lock(m1);
        global_var++;
        global_var--;
        // 虽然lock对象生命周期结束,会自动释放锁,但是
        // 得益于它的成员函数,这里进一步减少锁的粒度,在业务代码执行前释放锁
        if (lock.owns_lock()) {
            lock.unlock();
        }
        // 业务代码...
    }

}
int main() {
    thread t1(func);
    thread t2(func);
    t1.join();
    t2.join();
    printf("%d\n", global_var);// 0
    return 0;
}
2.std::recursive_mutex
  1. std::recursive_mutex:这是递归互斥锁,它允许同一线程多次获得互斥锁,可以用来解决同一个线程需要多次获得互斥量时导致死锁的问题。
    1. 问题演示:使用非递归独占互斥量发生死锁
    #include <iostream>
    #include <mutex>
    #include <thread>
    
    std::mutex m;
    int g_num = 0;
    
    void func() {
        m.lock();
        // 这里为了方便演示,实际上可能调用一个方法,方法内部再次对独占互斥锁进行了锁定
        m.lock();
        g_num++;
        m.unlock();
    }
    int main() {
    
        std::thread t1(func);
    
        t1.join();
    
        return 0;
    }
    
    1. 上述问题的解决方案:使用递归互斥锁,同时解锁的次数需要和上锁的次数相同。
    // 将上述的非递归独占锁换成递归锁
    std::recursive_mutex m;
    
  2. 递归互斥锁的缺点:
    1. 它的效率比非递归互斥锁低些
    2. 递归互斥锁虽然允许同一个线程多次获得同一个互斥锁的所有权,但最大次数并未具体说明,一旦超过一定的次数,就会抛出std::system错误。
3.std::timed_mutex
  1. timed_mutex:这是超时独占互斥锁,在获取互斥锁资源时,如果使用普通的互斥锁,线程可能一直阻塞下去。而使用超时独占互斥锁,线程阻塞了一个超时时长后,就会解除阻塞。
  2. timed_mutex:相比于普通独占互斥锁,多了两个成员函数:
    1. try_lock_for
    // 当线程获取不到互斥锁资源的时候,让线程阻塞一定的时间长度
    template <class _Rep, class _Period>
    bool try_lock_for(const chrono::duration<_Rep, _Period>& __rtime)
    函数返回值:
        当获得互斥锁的所有权后,就解除阻塞返回true
        阻塞的时长用完就返回false
    
    1. try_lock_until
    // 当线程获取不到互斥锁资源的时候,让线程阻塞到某一个指定的时间点
    template <class _Clock, class _Duration>
    bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime)
    函数返回值:
        当获得互斥锁的所有权后,就解除阻塞返回true
        阻塞到函数指定的时间点就返回false
    
  3. 使用示例
#include <thread>
#include <mutex>
#include <iostream>

class Test {
private:
    std::timed_mutex _m;
    int _i;
public:
    void worker() {
        while (1) {
            if (_m.try_lock_for(std::chrono::milliseconds(10))) {
                std::cout << "子线程id:" << std::this_thread::get_id() << "获取互斥锁所有权成功\n";
                // 对临界资源进行读写操作
                this->_i++; 
                //  模拟处理其他业务
                std::this_thread::sleep_for(std::chrono::seconds(5));
                _m.unlock();
                break;
            } else {
                std::cout << "子线程id:" << std::this_thread::get_id() << "获取互斥锁所有权失败\n";
            }
        }
        
    }
    Test(int i) : _i(i) {}
};
int main() {
    Test test(0);
    std::thread t1(&Test::worker, &test);
    std::thread t2(&Test::worker, &test);

    t1.join();
    t2.join();

    return 0;
}
4.std::shared_mutex
  1. 这是共享的互斥锁,适用于存在多个线程对共享资源读、少许线程对共享资源写的情况,使用共享互斥锁比普通互斥锁效率更高。等同于glibc库中的读写锁

线程同步之条件变量

条件变量能够阻塞一个或者多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程,条件变量需要和互斥量结合使用,防止多个线程同时对同一块内存进行读写操作。c++11中条件变量有两种:

  1. condition_variable
  2. condition_variable_any
1.condition_variable
  1. 等待函数wait:如果线程被函数阻塞,这个线程会释放占有的互斥锁的所有权,当阻塞解除之后这个线程会重新得到互斥锁的所有权,继续向下执行
#include <condition_variable>
// 调用wait函数的线程会被阻塞
void wait(unique_lock<mutex>& __lock) noexcept;

template<typename _Predicate>
void wait(unique_lock<mutex>& __lock, _Predicate __p)
函数参数:
    pred:是一个谓词,返回false则当前线程被阻塞,返回true则当前线程不会阻塞
  1. wait_for函数:和wait函数功能一样,区别在于假设当前阻塞的线程没有被其他线程唤醒,当阻塞一定的时长后,线程就会自动解除阻塞,继续向下运行。
cv_status
wait_for(unique_lock<mutex>& __lock,
   const chrono::duration<_Rep, _Period>& __rtime)
   
template<typename _Rep, typename _Period, typename _Predicate>
bool
wait_for(unique_lock<mutex>& __lock,
   const chrono::duration<_Rep, _Period>& __rtime,
   _Predicate __p)
  1. wait_until函数:指定让线程阻塞到某一个时间点,假设阻塞的线程没有被其他线程唤醒,当到达指定的时间点之后,线程就会自动解除阻塞,继续向下执行。
template<typename _Duration>
cv_status
wait_until(unique_lock<mutex>& __lock,
 const chrono::time_point<steady_clock, _Duration>& __atime)
 
template<typename _Clock, typename _Duration, typename _Predicate>
bool
wait_until(unique_lock<mutex>& __lock,
 const chrono::time_point<_Clock, _Duration>& __atime,
 _Predicate __p)
  1. 通知函数:
// 唤醒一个被当前条件变量阻塞的线程
void notify_one() noexcept;
// 唤醒全部被当前条件变量阻塞的线程
void notify_all() noexcept;
  1. 有关unique_lock的方法
// 锁定关联的互斥锁
void lock();
// 尝试锁定关联的互斥锁,若无法锁定,函数直接返回
bool try_lock();
// 试图锁定关联的可定时互斥锁,若互斥锁在给定时长中仍不能被锁定,函数返回
bool try_lock_for(const chrono::duration<_Rep, _Period>& __rtime);
// 试图锁定关联的可定时互斥锁,若互斥锁在到达给定的时间点后仍不能被锁定,函数返回
bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime);
// 将互斥锁解锁
void unlock();
  1. 使用condition_variable实现生产者消费者模型示例
// 使用条件变量实现一个生产者消费者模型
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <thread>

class TaskQueue {
private:
    int capacity;   // 任务队列的容量
    std::mutex m;   // 独占互斥锁
    std::condition_variable full;   // 阻塞生产者线程的条件变量
    std::condition_variable empty;  // 阻塞消费者线程的条件变量
    std::queue<int> m_queue;        // 存储任务的队列,任务为int型数据

public:
    TaskQueue(int capacity) : capacity(capacity) {}
    void put(const int& x) {
        std::unique_lock<std::mutex> locker(m);
        // 使用while是为了防止虚假唤醒的问题
        while (m_queue.size() == this->capacity) {
            full.wait(locker);
        }
        this->m_queue.push(x);
        std::cout << "生产者线程id:" << std::this_thread::get_id() << ",put x:" << x << std::endl;
        empty.notify_all();
    }

    void take() {
        std::unique_lock<std::mutex> locker(m);
        while (m_queue.empty()) {
            
            empty.wait(locker);
        }
        int x = this->m_queue.front();
        m_queue.pop();
        std::cout << "消费者线程id:" << std::this_thread::get_id() << ",take x:" << x << std::endl;
        full.notify_all();
    }


    // 获取当前任务队列的大小
    int size() {
        std::lock_guard<std::mutex> locker(m);
        return this->m_queue.size();
    }

    // 判断是否任务队列为空
    bool isEmpty() {
        std::lock_guard<std::mutex> locker(m);
        return m_queue.empty();
    }

    // 判断任务队列是否为满
    bool isFull() {
        std::lock_guard<std::mutex> locker(m);
        return m_queue.size() == this->capacity;
    }
};
int main() {
    TaskQueue task(100);

    // 生产者线程、消费者线程各5个
    std::thread producer[5], consume[5];

    for (int i = 0; i < 5; ++i) {
        producer[i] = std::thread(&TaskQueue::put, &task, i + 100);
        consume[i] = std::thread(&TaskQueue::take, &task);
    }

    for (int i = 0; i < 5; ++i) {
        producer[i].join();
        consume[i].join();
    }
    return 0;
}
  1. 使用另一个wait方法对上述程序进行改进:根据谓词判断线程是否需要阻塞。这么改进的优点是程序更加精简,没有了while循环;缺点是程序的可读性变差。
void put(const int& x) {
    std::unique_lock<std::mutex> locker(m);
    full.wait(locker, [this]() {
        return this->m_queue.size() != this->capacity;
    });
    this->m_queue.push(x);
    std::cout << "生产者线程id:" << std::this_thread::get_id() << ",put x:" << x << std::endl;
    empty.notify_all();
}

void take() {
    std::unique_lock<std::mutex> locker(m);
    empty.wait(locker, [this]() {
        return this->m_queue.size() != 0;
    });
    int x = this->m_queue.front();
    m_queue.pop();
    std::cout << "消费者线程id:" << std::this_thread::get_id() << ",take x:" << x << std::endl;
    full.notify_all();
}
2. condition_variable_any
  1. 线程阻塞函数:
    1. wait:可以传递给wait函数的互斥锁有前一节提到的四种互斥锁(任意一种互斥锁)
    2. wait_for
    3. wait_until
  2. 线程唤醒函数:
void notify_one() noexcept;
void notify_all() noexcept;
  1. 使用condition_variable_any实现生产者消费者模型
// 使用条件变量实现一个生产者消费者模型
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <thread>

class TaskQueue {
private:
    int capacity;   // 任务队列的容量
    std::mutex m;   // 独占互斥锁
    std::condition_variable_any full;   // 阻塞生产者线程的条件变量
    std::condition_variable_any empty;  // 阻塞消费者线程的条件变量
    std::queue<int> m_queue;        // 存储任务的队列,任务为int型数据

public:
    TaskQueue(int capacity) : capacity(capacity) {}
    void put(const int& x) {
        std::lock_guard<std::mutex> locker(m);
        full.wait(m, [this]() {
            return this->m_queue.size() != this->capacity;
        });
        this->m_queue.push(x);
        std::cout << "生产者线程id:" << std::this_thread::get_id() << ",put x:" << x << std::endl;
        empty.notify_all();
    }

    void take() {
        m.lock();
        empty.wait(m, [this]() {
            return this->m_queue.size() != 0;
        });
        int x = this->m_queue.front();
        m_queue.pop();
        std::cout << "消费者线程id:" << std::this_thread::get_id() << ",take x:" << x << std::endl;
        m.unlock();
        full.notify_all();
    }


    // 获取当前任务队列的大小
    int size() {
        std::lock_guard<std::mutex> locker(m);
        return this->m_queue.size();
    }

    // 判断是否任务队列为空
    bool isEmpty() {
        std::lock_guard<std::mutex> locker(m);
        return m_queue.empty();
    }

    // 判断任务队列是否为满
    bool isFull() {
        std::lock_guard<std::mutex> locker(m);
        return m_queue.size() == this->capacity;
    }
};
int main() {
    TaskQueue task(100);

    // 生产者线程、消费者线程各5个
    std::thread producer[5], consume[5];

    for (int i = 0; i < 5; ++i) {
        producer[i] = std::thread(&TaskQueue::put, &task, i + 100);
        consume[i] = std::thread(&TaskQueue::take, &task);
    }

    for (int i = 0; i < 5; ++i) {
        producer[i].join();
        consume[i].join();
    }
    return 0;
} 
3.两种互斥量的优缺点
  1. condition_variable只能和独占的非递归互斥锁mutex配合使用,有一定的局限性
  2. condition_variable_any可以和多种互斥锁使用,应用场景更广

线程同步之原子变量

c++11提供了一个原子类型std::atomic,通过这个原子类型管理的内部变量可以称之为原子变量。我们可以给原子类型指定bool、char、short、int、long、指针等类型作为模板参数。

1.atomic类的成员
  1. 修改或者设置原子变量的值,这是原子操作
// 1.使用重载的赋值运算符
operator=(_Tp __i)
// 2. 使用store方法
store(_Tp __i, memory_order __m = memory_order_seq_cst)
函数参数:  
    i:存储到原子变量中的值
    order:强制的内存次序
  1. 获取原子变量的值,这是原子操作
_Tp load(memory_order __m = memory_order_seq_cst)
2.类型别名

c++11中提供了很多类型别名,方便定义原子类型,例如:

/// atomic_bool
typedef atomic<bool>    atomic_bool;

/// atomic_char
typedef atomic<char>	atomic_char;
3.内存顺序约束
  1. 定义如下:
typedef enum memory_order
{
  memory_order_relaxed,
  memory_order_consume,
  memory_order_acquire,
  memory_order_release,
  memory_order_acq_rel,
  memory_order_seq_cst // 默认
} memory_order;
  1. 解释如下:
    image.png
    1. release:表示将数据写入到内存
    2. acquire:表示从内存读取数据
    3. memory_order_consume:是memory_order_acquire的加强版,它开销更小
    4. memory_order_seq_cst:是memory_order_rel的加强版
4.示例
  1. 使用了原子变量,可以代替使用独占互斥锁。示例如下
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>

class Counter {
public:
    void add() {
        for (int i = 0; i < 5; ++i) {
            m_value.fetch_add(1);
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
    }
    void sub() {
        for (int i = 0; i < 5; ++i) {
            m_value.fetch_add(-1);
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
    }

    int get()const {
        return m_value.load();
    }
private:
    std::atomic_int m_value{0};
};
int main() {
    Counter counter;

    std::thread t1(&Counter::add, &counter);
    std::thread t2(&Counter::sub, &counter);
    
    t1.join();
    t2.join();

    // 0
    std::cout << counter.get() << std::endl;
    return 0;
}

线程同步之信号量

使用c语言已有的信号量完成线程同步

线程异步

1.std::future
  1. 在多线程程序中的任务大都是异步的,主线程和子线程分别执行不同的任务。如果想要在主线程中获取某个子线程任务函数返回的结果可以使用C++11提供的future类。这个类需要和其它类或者函数搭配使用。
  2. future类的方法:
    1. 获取future对象内部保存的数据:get方法,这是一个阻塞函数。
    2. wait:阻塞当前的线程
    3. wait_for:线程阻塞一定的时长
    4. wait_until:线程阻塞到某一指定的时间点
    wait_until和wait_for的返回值
    /// Status code for futures
    enum class future_status
    {
        ready,  // 子线程中的任务已经执行完毕,结果已就绪
        timeout,// 子线程中的任务正在执行中,指定等待时长已用完 
        deferred// 子线程中的任务函仍未启动
    };
    
2.std::promise
  1. std::promise 是一个协助线程赋值的类,它能够将数据和future对象绑定起来,为获取线程函数中的某个值提供便利。
  2. 常用方法
    1. get_future:获取future对象
    2. set_value:存储要传出的value值
    3. set_value_at_thread_exit:存储要传出的value值,但是不立即令状态就绪。在当前线程退出时,子线程资源被销毁,再令状态就绪。
  3. 通过promise传递数据的过程如下:
    1. 在主线程中创建 std::promise 对象
    2. 将这个 std::promise 对象通过引用的方式传递给子线程的任务函数
    3. 在子线程任务函数中给 std::promise 对象赋值
    4. 在主线程中通过 std::promise 对象取出绑定的 future 实例对象
    5. 通过得到的 future 对象取出子线程任务函数中返回的值。
  4. 示例:
    1. 在子线程任务函数执行期间,让状态就绪
    #include <iostream>
    #include <future>
    #include <thread>
    
    int main() {
        std::promise<int> pr;
    
        std::thread t1([](std::promise<int>& p){
            p.set_value(1024);
            std::this_thread::sleep_for(std::chrono::seconds(3));
            std::cout << "waiting 3 seconds" << std::endl;
        }, std::ref(pr));
    
        std::future<int>f = pr.get_future();
        int ret = f.get();
        std::cout << "value:" << ret << std::endl;
        t1.join();
        return 0;
    }
    
    // 输出结果
    value:1024
    waiting 3 seconds
    
    1. 子线程任务函数执行结束,让状态就绪
    #include <iostream>
    #include <future>
    #include <thread>
    
    int main() {
        std::promise<int> pr;
    
        std::thread t1([](std::promise<int>& p){
            p.set_value_at_thread_exit(1024);
            std::this_thread::sleep_for(std::chrono::seconds(3));
            std::cout << "waiting 3 seconds" << std::endl;
        }, std::ref(pr));
    
        std::future<int>f = pr.get_future();
        int ret = f.get();
        std::cout << "value:" << ret << std::endl;
        t1.join();
        return 0;
    }
    
    // 输出结果
    waiting 3 seconds
    value:1024
    
3.std::packaged_task
  1. 这个类可以将内部包装的函数和future类绑定到一起,以便进行后续的异步调用,它和 std::promise 有点类似,std::promise内部保存一个共享状态的值,而 std::packaged_task 保存的是一个函数。
  2. 常用方法:
    1. get_future:获取future对象
  3. 示例:
#include <iostream>
#include <thread>
#include <future>
using namespace std;

int main()
{
    packaged_task<int(int)> task([](int x) {
        return x += 1024;
    });

    thread t1(ref(task), 0);

    future<int> f = task.get_future();
    int value = f.get();
    cout << "value: " << value << endl;

    t1.join();
    return 0;
}
4.std::async
  1. async是一个函数模板,在c++11中有两种调用方式:
    1. 方式1:直接调用传递到函数体内部的可调用对象,返回一个 future 对象
    future<__async_result_of<_Fn, _Args...>>
    async(_Fn&& __fn, _Args&&... __args);
    
    1. 方式2:通过指定的策略调用传递到函数内部的可调用对象,返回一个 future 对象
    future<__async_result_of<_Fn, _Args...>>
    async(launch __policy, _Fn&& __fn, _Args&&... __args);
    函数参数:
        policy:调用对象fn的执行策略
            std::launch::async:表示调用async函数时创建新的线程执行任务函数
            std::launch::deferred:调用async函数时不执行任务函数,直到调用了 future 的 get() 或者 wait() 时才执行任务。
            这种方式不会创建新的线程
    

线程局部存储TLS

  1. C++ 11标准提供了一个新的关键字thread_local来定义一个线程局部变量。
  2. 示例如下:
#include <iostream>
#include <thread>

// 定义线程局部变量
thread_local int g_number;

void read() {
    while (true) {
        std::cout << "g_number:" << g_number << "," << std::this_thread::get_id() << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

void write() {
    while (true) {
        g_number++;
        std::cout << "g_number:" << g_number << "," << std::this_thread::get_id() << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main() {
    std::thread t1(read);
    std::thread t2(write);

    if (t1.joinable()) t1.join();
    if (t2.joinable()) t2.join();
     
    return 0;
}