• C++11 多线程thread


    C++11通过标准库引入了对多线程的支持 。

     #include <thread>头文件,使用thread类

    一.创建线程及一些线程相关函数的使用

    1.创建线程:使用std::thread 类创建线程。构造thread类对象时,传入参数,构造完成后,新的线程直接被创建,同时执行该线程。在该对象销毁前,必须指定detach()或join()

    例:

    #include <qDebug>

    #include <thread>

    void Thread1()

    {

      qDebug()<< "Thread1 created";

    }

    void Thread2(int a, int &b)

    {

      qDebug()<< "thread2 created";

    }

    int main(int argc, char *argv[])

    {

      std::thread threadA(Thread1); //线程1创建,并执行。Thread1为将要在线程中执行的函数(不带参数)

      int a = 1;

      int b = 2;

      std::thread threadB(Thread2, a, std::ref(b));//线程2创建并执行。Thread2为函数,a为Thread2的第一个参数,b为Thread2的第二个参数。Thread2第二个参数为引用,这里需要用std::ref(b)的方式才是传引用

      return 1;

    }

    2.创建一个thread对象,在线程结束后对资源的回收:thread库有提供两个方法:1.thread::join   2.thread::detach。必须在thread对象销毁前调用

    join: 以阻塞的方式开启新线程。调用线程在新线程结束返回后才继续向下执行(只会阻塞调用线程,不会影响到其它线程)。新线程结束后join()会清理相关资源,然后返回, 此时join()清理了新线程的资源,thread对象与已经被销毁的线程没关联,调用thread::joinable返回false

    例:

    void Thread1(int* num, int time)

    {

      for (int i = 0; i < time; i++)

      {

        ++ *num;

      }

    }

    int main(int argc, char *argv[])

    {

      int num = 0;

      int time = 1000;

      std::thread mThread1(Thread1, &num, time);  //线程1被创建并执行

      std::thread mThread2(Thread1, &num, time);  //线程2被创建并执行

      mThread1.join(); //阻塞主线程,直到mThread1执行完后跑mThread2.join()。同时mThread2线程也在运行

      mThread2.join();

      qDebug()<< "num= "<< num;    //num的结果在1000-2000之间 但是实际跑出来的结果为什么就是2000???

      num = 0;

      std::thread mThread3(Thread1, &num, time);  //线程3被创建并执行

      mThread3.join(); //阻塞主线程,直到mThread3执行完后跑下一句

      std::thread mThread4(Thread1, &num, time);  //线程4被创建并执行

      mThread4.join();

      qDebug()<< "num= "<< num;    //num的结果为2000

    }

    detach: 非阻塞方式开启新线程。新线程与调用线程分离,且调用线程不能与新线程交互,新线程会在后台运行。线程的所有权,控制权和线程结束后其相关资源的回收都由C++运行库保证。调用thread::joinable返回false

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

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

    默认构造的thread对象;

    被移动后的thread对象;

    detach 或 join 后的thread对象;

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

    二.多线程数据共享安全问题

    多线程最主要的问题就是共享数据带来的问题。如果数据都是只读,多个线程同时访问不会有问题,但是,一个变量有多个线程会同时去修改,就会出现问题。

    对于这类问题,C++11标准提供互斥类解决。互斥类定义在mutex头文件中  #include <mutex>。通过对临界区域进行加锁提供对共享数据的保护。mutex.h中提供了4个互斥类:

    (1)、std::mutex:该类表示普通的互斥锁, 不能递归使用。

    (2)、std::timed_mutex:该类表示定时互斥锁,不能递归使用。std::time_mutex比std::mutex多了两个成员函数:

    A、try_lock_for():函数参数表示一个时间范围,在这一段时间范围之内线程如果没有获得锁则保持阻塞;如果在此期间其他线程释放了锁,则该线程可获得该互斥锁;如果超时(指定时间范围内没有获得锁),则函数调用返回false。

    B、try_lock_until():函数参数表示一个时刻,在这一时刻之前线程如果没有获得锁则保持阻塞;如果在此时刻前其他线程释放了锁,则该线程可获得该互斥锁;如果超过指定时刻没有获得锁,则函数调用返回false。
    (3)、std::recursive_mutex:该类表示递归互斥锁。递归互斥锁可以被同一个线程多次加锁,以获得对互斥锁对象的多层所有权。例如,同一个线程多个函数访问临界区时都可以各自加锁,执行后各自解锁。std::recursive_mutex释放互斥量时需要调用与该锁层次深度相同次数的unlock(),即lock()次数和unlock()次数相同。可见,线程申请递归互斥锁时,如果该递归互斥锁已经被当前调用线程锁住,则不会产生死锁。此外,std::recursive_mutex的功能与 std::mutex大致相同。

    (4)、std::recursive_timed_mutex:带定时的递归互斥锁。

    互斥类的最重要成员函数是lock()和unlock()。在进入临界区时,执行lock()加锁操作,如果这时已经被其它线程锁住,则当前线程在此排队等待。退出临界区时,执行unlock()解锁操作。

       (1)、构造函数:互斥类不支持copy和move操作,最初的互斥对象是处于unlocked状态。

            (2)、lock函数:互斥锁被锁定。线程申请该互斥锁,如果未能获得该互斥锁,则调用线程将阻塞(block)在该互斥锁上;如果成功获得该互诉锁,该线程一直拥有该互斥锁直到调用unlock解锁;如果该互斥锁已经被当前调用线程锁住,则产生死锁(deadlock)。

            (3)、unlock函数:解锁,释放调用线程对该互斥锁的所有权。

            (4)、try_lock:尝试锁定互斥锁。如果互斥锁被其他线程占有,则当前调用线程也不会被阻塞,而是由该函数调用返回false;如果该互斥锁已经被当前调用线程锁住,则会产生死锁。其中std::mutex就是lock、unlock。std::lock_guard与std::mutex配合使用,把锁放

                  到lock_guard中时,mutex自动上锁,lock_guard析构时,同时把mutex解锁。
             (5)、native_handle:返回当前句柄。

    例:

    #include <threa>

    #include <mutex>

    std::mutex mMutex

    void Thread1(int* num, int time)

    {

      for (int i = 0; i < time; i++)

      {

        mMutex.lock();

        ++ *num;

        mMutex.unlock();

      }

    }

    int main(int argc, char *argv[])

    {

      int num = 0;

      int time = 1000;

      std::thread mThread1(Thread1, &num, time);  //线程1被创建并执行

      std::thread mThread2(Thread1, &num, time);  //线程2被创建并执行

      mThread1.join(); //阻塞主线程,直到mThread1执行完后跑mThread2.join()。同时mThread2线程也在运行

      mThread2.join();

      qDebug()<< "num= "<< num;    //num的结果是2000

    }

    mutex对++ *num的保护,同一时刻,只能有一个线程对num变量进行++操作,因此,这段程序的输出必然是20000。

    ps:使用互斥锁一定要注意死锁的问题。

    但是实际跑出来的结果为什么就是2000???问题的猜测

    1.编译器对代码进行了优化,在循环中,变量num有可能直接放在寄存器中,不会每次++都从内存重新读取。循环结束后在将num写回内存。(但是后面验证使用了volatile关键字,结果也是2000)

    2.num是一个int的基本变量,num++的操作是一个原子操作,所以不用加锁。https://www.zhihu.com/question/27026846    //更有可能

    在该对象销毁前,必须指定detach()或join():

    if ( 1 )

    {

      thread jjj = thread(Func, param );

      jjj.detach();

    }

    如果在出if前没有指定jjj.detach(), 就会crash。

    对选用锁的类型的选择:

    开发过程中,对于多线程的情况下,单个基础数据类型的数据共享安全,尽量使用原子操作代替锁机制. 当需要对代码块进行数据安全保护的时候,就需要选择使用锁机制或者自旋锁了。

    参考:https://www.cnblogs.com/god-of-death/p/7843191.html

     
  • 相关阅读:
    Mycat之按照时间进行分片
    Mysql binlog解析器
    字体属性和文本属性总结
    css选择器
    CSS的三种引入方式
    CSS样式语法
    应用程序与数据库结合使用的三种方式
    存储过程
    子查询
    多表查询
  • 原文地址:https://www.cnblogs.com/linxisuo/p/13300930.html
Copyright © 2020-2023  润新知