C++11的多线程、function和bind、可变函数模板-1

简介: C++11的多线程、function和bind、可变函数模板

一、C++11多线程thread


1.1 线程thread

std::thread 在 #include 头文件中声明,因此使用 std::thread 时需要包含 #include 头文件。


1.1.1 构造函数

1、默认构造函数


//创建一个空的 thread 执行对象。
thread() _NOEXCEPT
   {   // construct with no thread
    _Thr_set_null(_Thr);
   }

2、初始化构造函数


//创建std::thread执行对象,该thread对象可被joinable,新产生的线程会调用threadFun函数,该函数的参数由 args 给出
template<class Fn, class... Args>
explicit thread(Fn&& fn, Args&&... args);

3、拷贝构造函数


// 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
thread(const thread&) = delete;
thread t1;
thread t2 =t1; // 错误

4、Move构造函数


//move 构造函数,调用成功之后, x 不代表任何 thread 执行对象。
//注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为detached。
thread(thread&& x)noexcept
thread t1;
thread t2 =move(t1); // 可以

5、范例


#include<thread>
#include<iostream>
using namespace std;
void threadFun(int &a) // 引用传递
{
 cout << "this is thread fun !" <<endl;
 cout <<" a = "<<(a+=10)<<endl;
}
int main()
{
 int x = 10;
 thread t1(threadFun, std::ref(x));     // std::ref(x) 创建了对变量 x 的引用。
 thread t2(std::move(t1)); // t1 线程失去所有权
 thread t3;
 t3 = std::move(t2); // t2 线程失去所有权
 t3.join();
 cout<<"Main End "<<"x = "<<x<<endl;
 return 0;
}
this is thread fun !
 a = 20
Main End x = 20

1.1.2 主要成员函数


  • get_id()

获取线程ID,返回类型std::thread::id对象。

http://www.cplusplus.com/reference/thread/thread/get_id/


  • - joinable()

判断线程是否可以加入等待

http://www.cplusplus.com/reference/thread/thread/joinable/


  • join()

等该线程执行完成后才返回。

http://www.cplusplus.com/reference/thread/thread/join/


  • detach()

detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。当线程主函数执行完之后,线程就结束了,运行时库负责清理与该线程相关的资源。

调用 detach 函数之后:

1)*this 不再代表任何的线程执行实例。

2)joinable() == false

3)get_id() == std::thread::id()

http://www.cplusplus.com/reference/thread/thread/detach/


1.1.3 范例示范

#include <iostream>
#include <thread> // 头文件
using namespace std;
// 1 传入0个值
void func1()
{
    cout << "func1 into" << endl;
}
// 2 传入2个值
void func2(int a, int b)
{
    cout << "func2 a + b = " << a+b << endl;
}
void func2_1(int a, int b)
{
    cout << "func2_1 a + b = " << a+b << endl;
}
int func2_1(string a, string b)
{
    cout << "func2_1 a + b = " << a << b<< endl;
    return 0;
}
// 3 传入引用
void func3(int &c) // 引用传递
{
    cout << "func3 c = " << &c << endl;
    c += 10;
}
//
class A
{
public:
//    4. 传入类函数
    void func4(int a)
    {
//        std::this_thread::sleep_for(std::chrono::seconds(1));
        cout << "thread:" << name_<< ", fun4 a = " << a << endl;
    }
    void func4(string str)
    {
//        std::this_thread::sleep_for(std::chrono::seconds(1));
        cout << "thread:" << name_<< ", fun4 str = " << str << endl;
    }
    void setName(string name) {
        name_ = name;
    }
    void displayName() {
        cout << "this:"  << this << ", name:" << name_ << endl;
    }
    void play()
    {
        std::cout<<"play call!"<<std::endl;
    }
private:
    string name_;
};
//5. detach
void func5()
{
    cout << "func5 into sleep " <<  endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    cout << "func5 leave " <<  endl;
}
// 6. move
void func6()
{
    cout << "this is func6 !" <<endl;
}

在mian函数里分别调用下面的各种情况

1)传入0个值


//    1. 传入0个值
   cout << "\n\n main1--------------------------\n";
   std::thread t1(func1);  // 只传递函数
   t1.join();  // 阻塞等待线程函数执行结束
 main1--------------------------
func1 into

2)传入2个值

第一种:


   int a =10;
   int b =20;
   std::thread t2(func2, a, b); // 加上参数传递,可以任意参数
   t2.join();
func2 a + b = 30

第二种:


    int a =10;
    int b =20;
    std::thread t2((void(*)(int, int)) func2_1, a, b); // 加上参数传递,可以任意参数
    t2.join();
func2_1 a + b = 30

第三种


    std::thread t2_2((int(*)(string, string)) func2_1, "zxm", " and mark"); // 加上参数传递,可以任意参数
    t2_2.join();
func2_1 a + b = zxm and mark

3)传入引用


   int c =10;
   std::thread t3(func3, std::ref(c)); // std::ref 加上参数传递,可以任意参数
   t3.join();
   cout << "main3 c = " << &c << ", "<<c << endl;
func3 c = 0x61fd34
main3 c = 0x61fd34, 20

4)传入类函数

重载1

   A * a4_ptr2 = new A();
    a4_ptr2->setName("king");
    std::thread t41((void(A::*)(int))&A::func4, a4_ptr2, 100);  // 重载void func4(int a),(A::*) 表示这是一个成员函数指针
    t41.join();
    delete a4_ptr2;
thread:king, fun4 a = 100

重载2


    A * a4_ptr3 = new A();
    a4_ptr3->setName("Darren");
    std::thread t43((void(A::*)(string))&A::func4, a4_ptr3, "and zxm"); // 重载 int func4(string str)
    t43.join();
    delete a4_ptr3;
thread:Darren, fun4 str = and zxm

5)detach


   std::thread t5(&func5);  // 只传递函数
   t5.detach();  // 脱离
    cout << "pid: " << t5.get_id() << endl; // t5此时不能管理线程了
   cout << "joinable: " << t5.joinable() << endl; // false
   // t5.join(); 这里不能用join了,会直接崩掉
   cout << "\n main5 end\n";
func5 into sleep 
pid: thread::id of a non-executing thread
joinable: 0
 main5 end

6)move


    int x = 10;
   thread t6_1(func6);
   thread t6_2(std::move(t6_1)); // t6_1 线程失去所有权
    // t6_1.join();  // 抛出异常   after throwing an instance of 'std::system_error'
    t6_2.join();
this is func6 !

1.2 互斥量

mutex又称互斥量,C++ 11中与 mutex相关的类(包括锁类型)和函数都声明在 头文件中,所以如果你需要使用 std::mutex,就必须包含 头文件。


C++11提供如下4种语义的互斥量(mutex)

1)std::mutex,独占的互斥量,不能递归使用。

2)std::time_mutex,带超时的独占互斥量,不能递归使用。

3)std::recursive_mutex,递归互斥量,不带超时功能。

4)std::recursive_timed_mutex,带超时的递归互斥量。


1.2.1 独占互斥量std::mutex

std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性— —即不支持递归地对std::mutex对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

(可重入互斥锁(Reentrant Mutex),也称为递归互斥锁,是一种特殊类型的互斥锁。它允许同一线程多次获得该锁而不会产生死锁。)


std::mutex 的成员函数

1)构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于unlocked状态的。


2)lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:


  • 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
  • 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。


3)unlock(), 解锁,释放对互斥量的所有权。


4)try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况

  • 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
  • 如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉。
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。


1.2.2 范例

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex
volatile int counter(0); // non-atomic counter
std::mutex mtx;           // locks access to counter
void increases_10k()
{
    for (int i=0; i<10000; ++i) {
        // 1. 使用try_lock的情况
       if (mtx.try_lock()) {   // only increase if currently not locked:
           ++counter;
           mtx.unlock();
       }
        // 2. 使用lock的情况
                // {
                //     mtx.lock();
                //     ++counter;
                //     mtx.unlock();
                // }
    }
}
int main()
{
    std::thread threads[10];
    for (int i=0; i<10; ++i)
        threads[i] = std::thread(increases_10k);
    for (auto& th : threads) th.join();
    std::cout << " successful increases of the counter "  << counter << std::endl;
    return 0;
}

使用try_lock的情况,不会阻塞

successful increases of the counter 15533

使用lock的情况,会阻塞

successful increases of the counter 100000

1.2.3 lock_guard和unique_lock的使用和区别

1、unique_lock,lock_guard的使用

自动释放锁


#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock_guard
#include <stdexcept>      // std::logic_error
std::mutex mtx;
void print_even (int x) {
    if (x%2==0) std::cout << x << " is even\n";
    else throw (std::logic_error("not even"));
}
void print_thread_id (int id) {
    try {
//        这里的lock_guard换成unique_lock是一样的。
        // using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
//        std::lock_guard<std::mutex> lck (mtx);
        std::unique_lock<std::mutex> lck (mtx);
        print_even(id);
    }
    catch (std::logic_error&) {
        std::cout << "[exception caught]\n";
    }
}
int main ()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i=0; i<10; ++i)
        threads[i] = std::thread(print_thread_id,i+1);
    for (auto& th : threads) th.join();
    return 0;
}
[exception caught]
2 is even
[exception caught]
4 is even
[exception caught]
6 is even
[exception caught]
8 is even
[exception caught]
10 is even

主要看std::unique_lock lck (mtx);,以往我们都是

mtx.lock();
print_even(id);
mtx.unlock();

需要我们手动释放锁,但在if else 这种多return分支中,很容易忘记释放。

而std::lock_guard lck (mtx);的lck是在栈上的,当退出作用域后会调用析构函数,自动释放锁。

std::unique_lock lck (mtx);也类似,能自动释放锁。


2、unique_lock,lock_guard的区别

1)unique_lock与lock_guard都能实现自动加锁和解锁,但是前者更加灵活,能实现更多的功能。

2)unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁,lck.lock()进行上锁,而不必等到析构时自动解锁。


#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <unistd.h>
std::deque<int> q;
std::mutex mu;
std::condition_variable cond;
int count = 0;
void fun1() {
    while (true) {
        std::unique_lock<std::mutex> locker(mu); 
        std::cout << "fun1 lock\n";
        q.push_front(count++);
        locker.unlock();        // lock_guard是没有手动释放锁的 unlock
        cond.notify_one();  
        sleep(1);
    }
}
void fun2() {
    while (true) {
        std::unique_lock<std::mutex> locker(mu);
        std::cout << "fun2 lock\n";
        std::cout << "fun2 wait into\n";
        cond.wait(locker, [](){return !q.empty();});
        std::cout << "fun2 wait leave\n";
        auto data = q.back();
        q.pop_back();
        locker.unlock(); 
        std::cout << "thread2 get value form thread1: " << data << std::endl;
    }
}
int main() {
    std::thread t1(fun1);
    std::thread t2(fun2);
    t1.join();
    t2.join();
    return 0;
}

上面代码中fun1插入数据(生产者),fun2pop出数据(消费者)。每次加锁之后,需要临时解锁,否则会造成死锁,即


std::unique_lock<std::mutex> locker(mu); 
…… 
locker.unlock();        // lock_guard是没有手动释放锁的 unlock

但是如果换成lock_guard,那就不行了,因此lock_guard是没有手动释放锁的 unlock接口。

当然,也可以加大括号的方式,出了大括号就相当于出了作用域。这时候两个都可以,因为不用unlock


    while (true) {
        {
            std::lock_guard<std::mutex> locker(mu); 
            std::cout << "fun1 lock\n";
            q.push_front(count++);
        }
        cond.notify_one();  
        sleep(1);
    }

1.3 条件变量

互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。但单靠互斥量无法实现线程的同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11对这种行为也提供了条件变量。条件变量位于头文件condition_variable下。


条件变量使用过程:

1)拥有条件变量的线程获取互斥量;

2)循环检查某个条件,如果条件不满足则阻塞直到条件满足;如果条件满足则向下执行;

3)某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或者所有等待线程。


1.3.1 wait函数

void wait (unique_lock<mutex>& lck);
template <class Predicate>
 void wait (unique_lock<mutex>& lck, Predicate pred);

包含两种重载,第一种只包含unique_lock对象,另外一个Predicate 对象(等待条件),这里必须使用unique_lock,因为wait函数的工作原理:

1)当前线程调用wait()后将被阻塞并且函数会解锁互斥量,直到另外某个线程调用notify_one或者notify_all唤醒当前线程;一旦当前线程获得通知(notify),wait()函数也是自动调用lock(),同理不能使用lock_guard对象。


2)如果wait()没有第二个参数,第一次调用默认条件不成立,直接解锁互斥量并阻塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait()重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后无条件地继续进行后面的操作。


3)如果wait()包含第二个参数,如果第二个参数不满足,那么wait()将解锁互斥量并堵塞到本行,直到某一个线程调用notify_one或notify_all为止,被唤醒后,wait()重新尝试获取互斥量,如果得不到,线程会卡在这里,直到获取到互斥量,然后继续判断第二个参数,如果表达式为false,wait()对互斥量解锁,然后休眠,如果为true,则进行后面的操作。


1.3.2 wait_for函数

template <class Rep, class Period>
 cv_status wait_for (unique_lock<mutex>& lck,
           const chrono::duration<Rep,Period>& rel_time);
template <class Rep, class Period, class Predicate>
   bool wait_for (unique_lock<mutex>& lck,
           const chrono::duration<Rep,Period>& rel_time, Predicate pred);

和wait不同的是,wait_for可以执行一个时间段,在线程收到唤醒通知或者时间超时之前,该线程都会处于阻塞状态,如果收到唤醒通知或者时间超时,wait_for返回,剩下操作和wait类似。


1.3.3 wait_until函数

template <class Clock, class Duration>
 cv_status wait_until (unique_lock<mutex>& lck,
            const chrono::time_point<Clock,Duration>& abs_time);
template <class Clock, class Duration, class Predicate>
   bool wait_until (unique_lock<mutex>& lck,
            const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);

与wait_for类似,只是wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点超时之前,该线程都会处于阻塞状态。如果超时或者收到唤醒通知,wait_until返回,剩下操作和wait类似


1.3.4 notify_one函数

void notify_one() noexcept;

解锁正在等待当前条件的线程中的一个,如果没有线程在等待,则函数不执行任何操作,如果正在等待的线程多余一个,则唤醒的线程是不确定的(随机唤醒一个)。


1.3.5 notify_all函数

void notify_all() noexcept;

解锁正在等待当前条件的所有线程,如果没有正在等待的线程,则函数不执行任何操作

目录
相关文章
|
17天前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
31 4
|
17天前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
29 3
|
20天前
|
编译器 C++
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
28 0
|
21天前
|
中间件 Docker Python
【Azure Function】FTP上传了Python Function文件后,无法在门户页面加载函数的问题
通过FTP上传Python Function至Azure云后,出现函数列表无法加载的问题。经排查,发现是由于`requirements.txt`中的依赖包未被正确安装。解决方法为:在本地安装依赖包到`.python_packages/lib/site-packages`目录,再将该目录内容上传至云上的`wwwroot`目录,并重启应用。最终成功加载函数列表。
|
2月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
JavaScript
箭头函数与普通函数(function)的区别
箭头函数是ES6引入的新特性,与传统函数相比,它有更简洁的语法,且没有自己的this、arguments、super或new.target绑定,而是继承自外层作用域。箭头函数不适用于构造函数,不能使用new关键字调用。
|
2月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
18 1
|
2月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
62 6
|
2月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
26 0
C++ 多线程之线程管理函数
|
2月前
|
JavaScript
箭头函数与普通函数(function)的区别
箭头函数是ES6引入的新语法,相比传统函数表达式更简洁,且没有自己的this、arguments、super或new.target绑定,而是继承自外层作用域。这使得箭头函数在处理回调和闭包时更加灵活方便。

热门文章

最新文章