精华内容
下载资源
问答
  • std::thread构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数。 第一参数的类型并不是c语言中的函数指针(c语言传递函数都是...

    构造函数的参数

    std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数

    第一参数的类型并不是c语言中的函数指针(c语言传递函数都是使用函数指针),在c++11中,增加了可调用对象(Callable Objects)的概念,总的来说,可调用对象可以是以下几种情况:

    • 函数指针
    • 重载了operator()运算符的类对象,即仿函数
    • lambda表达式(匿名函数)
    • std::function

    函数指针示例

    // 普通函数 无参
    void function_1() {
    }
    
    // 普通函数 1个参数
    void function_2(int i) {
    }
    
    // 普通函数 2个参数
    void function_3(int i, std::string m) {
    }
    
    std::thread t1(function_1);
    std::thread t2(function_2, 1);
    std::thread t3(function_3, 1, "hello");
    
    t1.join();
    t2.join();
    t3.join();
    

    实验的时候还发现一个问题,如果将重载的函数作为线程的入口函数,会发生编译错误!编译器搞不清楚是哪个函数,如下面的代码:

    // 普通函数 无参
    void function_1() {
    }
    
    // 普通函数 1个参数
    void function_1(int i) {
    }
    std::thread t1(function_1);
    t1.join();
    // 编译错误
    /*
    C:\Users\Administrator\Documents\untitled\main.cpp:39: 
    error: no matching function for call to 'std::thread::thread(<unresolved overloaded function type>)'
         std::thread t1(function_1);
                                  ^
    */
    
    

    仿函数

    // 仿函数
    class Fctor {
    public:
        // 具有一个参数
        void operator() () {
    
        }
    };
    Fctor f;
    std::thread t1(f);  
    // std::thread t2(Fctor()); // 编译错误 
    std::thread t3((Fctor())); // ok
    std::thread t4{Fctor()}; // ok
    

    一个仿函数类生成的对象,使用起来就像一个函数一样,比如上面的对象f,当使用f()时就调用operator()运算符。所以也可以让它成为线程类的第一个参数,如果这个仿函数有参数,同样的可以写在线程类的后几个参数上。

    t2之所以编译错误,是因为编译器并没有将Fctor()解释为一个临时对象,而是将其解释为一个函数声明,编译器认为你声明了一个函数,这个函数不接受参数,同时返回一个Factor对象。解决办法就是在Factor()外包一层小括号(),或者在调用std::thread的构造函数时使用{},这是c++11中的新的同意初始化语法。

    但是,如果重载的operator()运算符有参数,就不会发生上面的错误。

    匿名函数

    std::thread t1([](){
        std::cout << "hello" << std::endl;
    });
    
    std::thread t2([](std::string m){
        std::cout << "hello " << m << std::endl;
    }, "world");
    

    std::function

    class A{
    public:
        void func1(){
        }
    
        void func2(int i){
        }
        void func3(int i, int j){
        }
    };
    
    A a;
    std::function<void(void)> f1 = std::bind(&A::func1, &a);
    std::function<void(void)> f2 = std::bind(&A::func2, &a, 1);
    std::function<void(int)> f3 = std::bind(&A::func2, &a, std::placeholders::_1);
    std::function<void(int)> f4 = std::bind(&A::func3, &a, 1, std::placeholders::_1);
    std::function<void(int, int)> f5 = std::bind(&A::func3, &a, std::placeholders::_1, std::placeholders::_2);
    
    std::thread t1(f1);
    std::thread t2(f2);
    std::thread t3(f3, 1);
    std::thread t4(f4, 1);
    std::thread t5(f5, 1, 2);
    

    传值还是引用

    先提出一个问题:如果线程入口函数的的参数是引用类型,在线程内部修改该变量,主线程的变量会改变吗?

    代码如下:

    #include <iostream>
    #include <thread>
    #include <string>
    
    // 仿函数
    class Fctor {
    public:
        // 具有一个参数 是引用
        void operator() (std::string& msg) {
            msg = "wolrd";
        }
    };
    
    
    
    int main() {
        Fctor f;
        std::string m = "hello";
        std::thread t1(f, m);
    
        t1.join();
        std::cout << m << std::endl;
        return 0;
    }
    
    // vs下: 最终是:"hello"
    // g++编译器: 编译报错
    

    事实上,该代码使用g++编译会报错,而使用vs2015并不会报错,但是子线程并没有成功改变外面的变量m

    我是这么认为的:std::thread类,内部也有若干个变量,当使用构造函数创建对象的时候,是将参数先赋值给这些变量,所以这些变量只是个副本,然后在线程启动并调用线程入口函数时,传递的参数只是这些副本,所以内部怎么操作都是改变副本,而不影响外面的变量。g++可能是比较严格,这种写法可能会导致程序发生严重的错误,索性禁止了。

    而如果可以想真正传引用,可以在调用线程类构造函数的时候,用std::ref()包装一下。如下面修改后的代码:

    std::thread t1(f, std::ref(m));
    

    然后vsg++都可以成功编译,而且子线程可以修改外部变量的值。

    当然这样并不好,多个线程同时修改同一个变量,会发生数据竞争。

    同理,构造函数的第一个参数是可调用对象,默认情况下其实传递的还是一个副本。

    #include <iostream>
    #include <thread>
    #include <string>
    
    class A {
    public:
        void f(int x, char c) {}
        int g(double x) {return 0;}
        int operator()(int N) {return 0;}
    };
    
    void foo(int x) {}
    
    int main() {
        A a;
        std::thread t1(a, 6); // 1. 调用的是 copy_of_a()
        std::thread t2(std::ref(a), 6); // 2. a()
        std::thread t3(A(), 6); // 3. 调用的是 临时对象 temp_a()
        std::thread t4(&A::f, a, 8, 'w'); // 4. 调用的是 copy_of_a.f()
        std::thread t5(&A::f, &a, 8, 'w'); //5.  调用的是 a.f()
        std::thread t6(std::move(a), 6); // 6. 调用的是 a.f(), a不能够再被使用了
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        t5.join();
        t6.join();
        return 0;
    }
    

    对于线程t1来说,内部调用的线程函数其实是一个副本,所以如果在函数内部修改了类成员,并不会影响到外面的对象。只有传递引用的时候才会修改。所以在这个时候就必须想清楚,到底是传值还是传引用!

    线程对象只能移动不可复制

    线程对象之间是不能复制的,只能移动,移动的意思是,将线程的所有权在std::thread实例间进行转移。

    void some_function();
    void some_other_function();
    std::thread t1(some_function);
    // std::thread t2 = t1; // 编译错误
    std::thread t2 = std::move(t1); //只能移动 t1内部已经没有线程了
    t1 = std::thread(some_other_function); // 临时对象赋值 默认就是移动操作
    std::thread t3;
    t3 = std::move(t2); // t2内部已经没有线程了
    t1 = std::move(t3); // 程序将会终止,因为t1内部已经有一个线程在管理了

     

    展开全文
  • 移动构造函数和移动赋值

    千次阅读 2018-02-27 20:32:00
    移动构造函数可以弥补拷贝构造函数的空缺。 移动语义,简单来说解决的是各种情形下对象的资源所有权转移的问题。而在C++11之前,移动语义的缺失是C++饱受诟病的问题之一。 举个栗子。 问题一:如何将大象放入冰箱...

    一、概述

    移动构造函数可以弥补拷贝构造函数的空缺。

    移动语义,简单来说解决的是各种情形下对象的资源所有权转移的问题。而在C++11之前,移动语义的缺失是C++饱受诟病的问题之一。

    举个栗子。

    问题一:如何将大象放入冰箱?
    答案是众所周知的。首先你需要有一台特殊的冰箱,这台冰箱是为了装下大象而制造的。你打开冰箱门,将大象放入冰箱,然后关上冰箱门。

    问题二:如何将大象从一台冰箱转移到另一台冰箱?
    普通解答:打开冰箱门,取出大象,关上冰箱门,打开另一台冰箱门,放进大象,关上冰箱门。
    2B解答:在第二个冰箱中启动量子复制系统,克隆一只完全相同的大象,然后启动高能激光将第一个冰箱内的大象气化消失。
    等等,这个2B解答听起来很耳熟,这不就是C++中要移动一个对象时所做的事情吗?

    “移动”,这是一个三岁小孩都明白的概念。将大象(资源)从一台冰箱(对象)移动到另一台冰箱,这个行为是如此自然,没有任何人会采用先复制大象,再销毁大象这样匪夷所思的方法。C++通过拷贝构造函数和拷贝赋值操作符为类设计了拷贝/复制的概念,但为了实现对资源的移动操作,调用者必须使用先复制、再析构的方式。否则,就需要自己实现移动资源的接口。

    为了实现移动语义,首先需要解决的问题是,如何标识对象的资源是可以被移动的呢?这种机制必须以一种最低开销的方式实现,并且对所有的类都有效。C++的设计者们注意到,大多数情况下,右值所包含的对象都是可以安全的被移动的。

    右值(相对应的还有左值)是从C语言设计时就有的概念,但因为其如此基础,也是一个最常被忽略的概念。不严格的来说,左值对应变量的存储位置,而右值对应变量的值本身。C++中右值可以被赋值给左值或者绑定到引用。类的右值是一个临时对象,如果没有被绑定到引用,在表达式结束时就会被废弃。于是我们可以在右值被废弃之前,移走它的资源进行废物利用,从而避免无意义的复制。被移走资源的右值在废弃时已经成为空壳,析构的开销也会降低。

    右值中的数据可以被安全移走这一特性使得右值被用来表达移动语义。以同类型的右值构造对象时,需要以引用形式传入参数。右值引用顾名思义专门用来引用右值,左值引用和右值引用可以被分别重载,这样确保左值和右值分别调用到拷贝和移动的两种语义实现。对于左值,如果我们明确放弃对其资源的所有权,则可以通过std::move()来将其转为右值引用。std::move()实际上是static_cast<T&&>()的简单封装。

    右值引用至少可以解决以下场景中的移动语义缺失问题:

    • 按值传入参数
    按值传参是最符合人类思维的方式。基本的思路是,如果传入参数是为了将资源交给函数接受者,就应该按值传参。同时,按值传参可以兼容任何的cv-qualified左值、右值,是兼容性最好的方式。

    其使用方式如下:

     1 class People {
     2 public:
     3   People(string name) // 按值传入字符串,可接收左值、右值。接收左值时为复制,接收右值时为移动
     4   : name_(move(name)) // 显式移动构造,将传入的字符串移入成员变量
     5   {
     6   }
     7   string name_;
     8 };
     9 
    10 People a("Alice"); // 移动构造name
    11 
    12 string bn = "Bob";
    13 People b(bn); // 拷贝构造name
    14 

    构造a时,调用了一次字符串的构造函数和一次字符串的移动构造函数。如果使用const string& name接收参数,那么会有一次构造函数和一次拷贝构造,以及一次non-trivial的析构。尽管看起来很蛋疼,尽管编译器还有优化,但从语义来说按值传入参数是最优的方式。

    如果你要在构造函数中接收std::shared_ptr<X>并且存入类的成员(这是非常常见的),那么按值传入更是不二选择。拷贝std::shared_ptr<X>需要线程同步,相比之下移动std::shared_ptr是非常轻松愉快的。

    • 按值返回
    和接收输入参数一样,返回值按值返回也是最符合人类思维的方式。曾经有无数函数为了返回容器而不得不写成这样
    void str_split(const string& s, vector<string>* vec); // 一个按值语义定义的字符串拆分函数。这里不考虑分隔符,假定分隔符是固定的。

    这样要求vec在外部被事先构造,此时尚无从得知vec的大小。即使函数内部有办法预测vec的大小,因为函数并不负责构造vec,很可能仍需要resize。
    对这样的函数嵌套调用更是痛苦的事情,谁用谁知道啊。

    有了移动语义,就可以写成这样

    1 vector<string> str_split(const string& s) {
    2   vector<string> v;
    3   // ...
    4   return v; // v是左值,但优先移动,不支持移动时仍可复制。
    5 }

    如果函数按值返回,return语句又直接返回了一个栈上的左值对象(输入参数除外)时,标准要求优先调用移动构造函数,如果不符再调用拷贝构造函数。尽管v是左值,仍然会优先采用移动语义,返回vector<string>从此变得云淡风轻。此外,无论移动或是拷贝,可能的情况下仍然适用编译器优化,但语义不受影响。

    对于std::unique_ptr来说,这简直就是福音。
    1 unique_ptr<SomeObj> create_obj(/*...*/) {
    2   unique_ptr<SomeObj> ptr(new SomeObj(/*...*/));
    3   ptr->foo(); // 一些可能的初始化
    4   return ptr;
    5 }

    当然还有更简单的形式

    unique_ptr<SomeObj> create_obj(/*...*/) {
      return unique_ptr<SomeObj>(new SomeObj(/*...*/));
    }

    二、使用方法

    基类:

     1 class MemoryBlock  
     2 {  
     3 public:  
     4   
     5    // Simple constructor that initializes the resource.  
     6    explicit MemoryBlock(size_t length)  
     7       : _length(length)  
     8       , _data(new int[length])  
     9    {  
    10       std::cout << "In MemoryBlock(size_t). length = "  
    11                 << _length << "." << std::endl;  
    12    }  
    13   
    14    // Destructor.  
    15    ~MemoryBlock()  
    16    {  
    17       std::cout << "In ~MemoryBlock(). length = "  
    18                 << _length << ".";  
    19   
    20       if (_data != nullptr)  
    21       {  
    22          std::cout << " Deleting resource.";  
    23          // Delete the resource.  
    24          delete[] _data;  
    25       }  
    26   
    27       std::cout << std::endl;  
    28    }  
    29   
    30    // Copy constructor.  
    31    MemoryBlock(const MemoryBlock& other)  
    32       : _length(other._length)  
    33       , _data(new int[other._length])  
    34    {  
    35       std::cout << "In MemoryBlock(const MemoryBlock&). length = "   
    36                 << other._length << ". Copying resource." << std::endl;  
    37   
    38       std::copy(other._data, other._data + _length, _data);  
    39    }  
    40   
    41    // Copy assignment operator.  
    42    MemoryBlock& operator=(const MemoryBlock& other)  
    43    {  
    44       std::cout << "In operator=(const MemoryBlock&). length = "   
    45                 << other._length << ". Copying resource." << std::endl;  
    46   
    47       if (this != &other)  
    48       {  
    49          // Free the existing resource.  
    50          delete[] _data;  
    51   
    52          _length = other._length;  
    53          _data = new int[_length];  
    54          std::copy(other._data, other._data + _length, _data);  
    55       }  
    56       return *this;  
    57    }  
    58   
    59    // Retrieves the length of the data resource.  
    60    size_t Length() const  
    61    {  
    62       return _length;  
    63    }  
    64   
    65 private:  
    66    size_t _length; // The length of the resource.  
    67    int* _data; // The resource.  
    68 };  

    移动构造函数:

    1. 定义一个空的构造函数方法,该方法采用一个对类类型的右值引用作为参数,如以下示例所示:
    2. 在移动构造函数中,将源对象中的类数据成员添加到要构造的对象:
    3. 将源对象的数据成员分配给默认值。 这可以防止析构函数多次释放资源(如内存):
       1 // Move constructor.  
       2 MemoryBlock(MemoryBlock&& other)  
       3    : _data(nullptr)  
       4    , _length(0)  
       5 {  
       6    std::cout << "In MemoryBlock(MemoryBlock&&). length = "   
       7              << other._length << ". Moving resource." << std::endl;  
       8   
       9    // Copy the data pointer and its length from the   
      10    // source object.  
      11    _data = other._data;  
      12    _length = other._length;  
      13   
      14    // Release the data pointer from the source object so that  
      15    // the destructor does not free the memory multiple times.  
      16    other._data = nullptr;  
      17    other._length = 0;  
      18 }  

       

    移动赋值运算符:

    1、定义一个空的赋值运算符,该运算符采用一个对类类型的右值引用作为参数并返回一个对类类型的引用,
    2、在移动赋值运算符中,如果尝试将对象赋给自身,则添加不执行运算的条件语句。

    3、在条件语句中,从要将其赋值的对象中释放所有资源(如内存)。以下示例从要将其赋值的对象中释放 _data 成员,执行第一个过程中的步骤 2 和步骤 3 以将数据成员从源对象转移到要构造的对象,

    4、返回对当前对象的引用,如以下示例所示:

     1 MemoryBlock& operator=(MemoryBlock&& other)  
     2 {  
     3    std::cout << "In operator=(MemoryBlock&&). length = "   
     4              << other._length << "." << std::endl;  
     5   
     6    if (this != &other)  
     7    {  
     8       // Free the existing resource.  
     9       delete[] _data;  
    10   
    11       // Copy the data pointer and its length from the   
    12       // source object.  
    13       _data = other._data;  
    14       _length = other._length;  
    15   
    16       // Release the data pointer from the source object so that  
    17       // the destructor does not free the memory multiple times.  
    18       other._data = nullptr;  
    19       other._length = 0;  
    20    }  
    21    return *this;  
    22 }  

     

    三、意义及其与拷贝构造的区别
    对于左值进行的构造,可以直接进行 深拷贝,这使得每个构造出来的类均具有自己的堆空间。而对于使用右值进行的构造,由于右值为临时变量,其内部的堆内存在构造完成后即被释放,这时就可以使用移动构造函数进行构造,直接将右值内的堆空间赋值给新的类对象,并将原有的临时类内的指针=nullptr,这样再执行右值的析构函数时就不会释放掉原本的堆内存,避免了内存的重复创建和释放。
    四、默认合成移动构造函数
     1 class A
     2 {
     3 public:
     4     int *p = new int;
     5     string str = "Fuck";
     6 };
     7 
     8 int main()
     9 {
    10     A a;
    11     A b(std::move(a));
    12     cout << a.p << " " << a.str << " ";
    13     cout << b.p << " "<<b.str << " ";
    14     system("pause");
    15 }
    结果输出 a.p与b.p地址一样的,而对于a.str显示为空而b.str显示为Fuck.只有string的资源转移了
    个人理解:C++编译器合成的默认移动函数对于基本类型所谓移动只是把其值拷贝,对于string这类类成员中因为其类实现了移动构造函数才会真正的所谓资源移动。
    另外只要定义了析构函数,那就不会默认生成移动构造函数,因为对于基本类型来说是"复制"而非真正的移动,只要其一析构了,另外一个的资源也就没了.所以只要定义了析构函数,就不会合成移动构造~(刚刚测试了,定义了析构,结果输出了2个fuck,确实是这样)
    参考:
    展开全文
  • C++11 thread 2.线程构造函数

    千次阅读 2019-10-29 13:39:58
    (4)移动构造函数 构造一个线程对象,该对象获取x表示的执行线程(如果有)。 该操作不会以任何方式影响被移动线程的执行,它只是传输其处理程序。 x对象不再代表任何执行线程。 可连接的线程对象应在销毁之前...
    default (1)
    thread() noexcept;
    
    initialization (2)
    template <class Fn, class... Args>
    explicit thread (Fn&& fn, Args&&... args);
    
    copy [deleted] (3)
    thread (const thread&) = delete;
    
    move (4)
    thread (thread&& x) noexcept;

    Construct thread

    Constructs a thread object:

    (1) default constructor

    Construct a thread object that does not represent any thread of execution.

    (2) initialization constructor

    Construct a thread object that represents a new joinable thread of execution.
    The new thread of execution calls fn passing args as arguments (using decay copies of its lvalue or rvaluereferences).
    The completion of this construction synchronizes with the beginning of the invocation of this copy of fn.

    (3) copy constructor

    Deleted constructor form (thread objects cannot be copied).

    (4) move constructor

    Construct a thread object that acquires the thread of execution represented by x (if any). This operation does not affect the execution of the moved thread in any way, it simply transfers its handler.
    The x object no longer represents any thread of execution.

     

    构造线程
    构造一个线程对象:

    (1)默认构造函数
    构造一个不代表任何执行线程的线程对象。
    (2)初始化构造函数
    构造一个代表新的可连接执行线程的线程对象。
    新的执行线程调用fn传递args作为参数(使用其左值或右值引用的衰减副本)。
    该构造的完成与该fn副本的调用开始同步。
    (3)复制构造函数
    删除的构造函数形式(无法复制线程对象)。
    (4)移动构造函数
    构造一个线程对象,该对象获取x表示的执行线程(如果有)。 该操作不会以任何方式影响被移动线程的执行,它只是传输其处理程序。
    x对象不再代表任何执行线程。

    可连接的线程对象应在销毁之前被连接或分离。

    Parameters

    fn

    A pointer to function, pointer to member, or any kind of move-constructible function object (i.e., an object whose class defines operator(), including closures and function objects).
    The return value (if any) is ignored.

    args...

    Arguments passed to the call to fn (if any). Their types shall be move-constructible.
    If fn is a member pointer, the first argument shall be an object for which that member is defined (or a reference, or a pointer to it).

    x

    thread object whose state is moved to the constructed object.

    Fn and Args... are template parameters: if implicitly deduced, these are the proper lvalue or rvalue reference type to bind the arguments to. Note though, that on the call to fn in the new thread, decay copies of fn and args... are always used (see std::ref for a wrapper class that makes references copyable).

    参数
    fn
    指向函数的指针,指向成员的指针或任何类型的可移动构造的函数对象(即,其类定义了operator()的对象,包括闭包和函数对象)。
    返回值(如果有)将被忽略。
    args ...
    传递给fn调用的参数(如果有)。 它们的类型应是可移动构造的。
    如果fn是成员指针,则第一个参数应是为其定义了该成员的对象(或其引用或指向它的指针)。
    X
    线程对象,其状态已移至构造对象。
    Fn和Args ...是模板参数:如果隐式推导,则它们是将参数绑定到的正确的左值或右值引用类型。 但是请注意,在新线程中调用fn时,总是使用fn和args的衰减副本...(有关使引用可复制的包装器类,请参见std :: ref)。

    1.使用类的成员函数构造线程

    #include <iostream>       // std::cout
    #include <thread>         // std::thread, std::this_thread::sleep_for
    #include <chrono>         // std::chrono::seconds
    using namespace std;
    class CThreadCls
    {
    public:
    	void fn(int num,int num2);
    };
    
    void CThreadCls::fn(int num1,int num2)
    {
    	cout << "This is CThreadCls::fn num1=" << num1 << " num2=" << num2 << endl;
    }
    int main()
    {
    	CThreadCls cls;
    	thread t1(&CThreadCls::fn, &cls, 1, 2);
    	t1.join();
    	return 0;
    }

    运行结果:

    2. std::thread::swap

    交换线程
    用x交换对象的状态。

    // threadtest.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <iostream>
    #include <thread>
    #include <chrono>
    
    void foo()
    {
    	std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    void bar()
    {
    	std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    int main()
    {
    	std::thread t1(foo);
    	std::thread t2(bar);
    
    	std::cout << "thread 1 id: " << t1.get_id() << std::endl;
    	std::cout << "thread 2 id: " << t2.get_id() << std::endl;
    
    	std::swap(t1, t2);
    
    	std::cout << "after std::swap(t1, t2):" << std::endl;
    	std::cout << "thread 1 id: " << t1.get_id() << std::endl;
    	std::cout << "thread 2 id: " << t2.get_id() << std::endl;
    
    	t1.swap(t2);
    
    	std::cout << "after t1.swap(t2):" << std::endl;
    	std::cout << "thread 1 id: " << t1.get_id() << std::endl;
    	std::cout << "thread 2 id: " << t2.get_id() << std::endl;
    
    	t1.join();
    	t2.join();
    }
    
    

    交换线程:运行结果:

    3.std::thread::get_id 

    Get thread id

    Returns the thread id.

    If the thread object is joinable, the function returns a value that uniquely identifies the thread.

    If the thread object is not joinable, the function returns a default-constructed object of member type thread::id.

    获取线程ID
    返回线程ID。

    如果线程对象是可连接的,则该函数返回一个唯一标识线程的值。

    如果线程对象不可连接,则该函数返回成员类型为thread :: id的默认构造的对象。

    std::thread::native_handle

    native_handle_type native_handle();
    Get native handle
    This member function is only present in class thread if the library implementation supports it.
    只有库函数支持该函数时该方法才会有效。

    If present, it returns a value used to access implementation-specific information associated to the thread.
    如果有效,用于获得与操作系统相关的原生线程句柄

    例子:


    #include <iostream>
    #include <thread>
    #include <ctime>
    using namespace std;
    void show(int n){
        cout<<"n="<<n<<endl;
    }
    int main()
    {
        thread t(show,18);
        cout<<"t.get_id="<<t.get_id()<<endl;
        auto tn=t.native_handle();
        t.join();
        cout<<"tn="<<tn<<endl;        
    }
    运行截图:


     

    展开全文
  • Java 中提供了丰富的构造函数,在本文中,将介绍每一个构造函数,以及分析一些可能你未关注的细节 线程的命名 在构造线程的时候可以为线程起一个特殊意义的名字,有利用实际问题的排查和线程跟踪 线程的默认命名 ...

    Java 中提供了丰富的构造函数,在本文将介绍每一个构造函数,以及分析一些可能你未关注的细节
    在这里插入图片描述

    线程的命名

    在构造线程的时候可以为线程起一个特殊意义的名字,有利于实际问题的排查和线程跟踪

    线程的默认命名

        public Thread() {
            init(null, null, "Thread-" + nextThreadNum(), 0);
        }
        
        private static int threadInitNumber;
        private static synchronized int nextThreadNum() {
            return threadInitNumber++;
        }
    

    如果没有给线程显示命名,那么线程将以“Thread-”为前缀与一个自增的数字进行组合

    命名线程
    Thread提供了如下的关于命名的构造函数,通过构造一个友好的名称是一个很好的实战方式

    Thread(String name)
    Thread(Runnable target, String name)
    Thread(ThreadGroup group, String name)
    Thread(ThreadGroup group, Runnable target, String name)
    Thread(ThreadGroup group, Runnable target, String name, long stackSize)
    

    修改线程名字
    无论是使用默认的命名规则,还是指定一个特殊的名字,在线程启动之前都有一次机会进行修改,一旦线程启动之后,名字就不能修改了

        public final synchronized void setName(String name) {
            checkAccess();
            if (name == null) {
                throw new NullPointerException("name cannot be null");
            }
    
            this.name = name;
            if (threadStatus != 0) {                // 当线程状态不是New的时候,不允许修改线程名称
                setNativeName(name);
            }
        }
    

    线程的父子关系

    通过源码,我们发现Thread 的所有构造函数,最终都会去调用一个静态方法init

        private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
            if (name == null) {
                throw new NullPointerException("name cannot be null");
            }
    
            this.name = name;
    
            Thread parent = currentThread();                 // 获取当前线程作为父线程
            SecurityManager security = System.getSecurityManager();
            .......
            }
    

    其中currentThrea() 是获取当前线程,我们知道在线程生命周期中,线程最初的状态为New,没有执行start方法,它只能算一个Thread实例,这里currentThrea() 代表的是创建它的那个线程,因此我们可以得出

    • 一个线程的创建肯定是由另外一个线程完成的
    • 被创建的父线程是创建它的线程

    我们知道main函数所在的线程是JVM创建,也就是main线程,意味我们之前创建所有线程的父线程都是main线程

    Thread 与 ThreadGroup

    通过继续阅读源码发现在Thread的创建中,我们可以显示的指定线程的Group

            SecurityManager security = System.getSecurityManager();
            if (g == null) {
                /* Determine if it's an applet or not */
    
                /* If there is a security manager, ask the security manager
                   what to do. */
                if (security != null) {
                    g = security.getThreadGroup();
                }
    
                /* If the security doesn't have a strong opinion of the matter
                   use the parent thread group. */
                if (g == null) {
                    g = parent.getThreadGroup();
                }
            }
    

    当构造Thread没有显示指定ThreadGroup时候,子线程将会加入父线程所在的线程组

    • main线程所在的ThreadGroup 称为main
    • 构造一个线程的时候如果没有显示指定ThreadGroup,那它将会和父线程同属于一个ThreadGroup

    默认设置中,子线程会和父线程除了同属于一个Group之外,它还会和父线程拥有同样的优先级,同样的daemon

    Thread 与 Runnable

    Thread负责线程相关的职责和控制,而Runnable负责逻辑执行单元部分

    Thread 与 虚拟机栈

    在Thread的构造函数中,我们发现一个特殊的参数stackSize,这个参数的作用是什么?
    一般情况下,创建线程的时候不需要手动指定栈内存的地址空间字节数组,但通过stacksize的设置可以发现,stackSize越大,线程内方法调用递归的深度就越深,stacksize越小创建的线程数量越多,当然这个参数高度依赖平台

    JVM内存结构
    JVM在执行Java程序的时候会把对应的物理内存划分不同的区域,每个区域都存放不同的数据,也有不同的创建和销毁时机,有些分区在JVM启动的时候就创建,有些则在运行才创建,java1.8之前,JVM内存结构大致如下图所示:
    在这里插入图片描述
    程序计数器
    程序计数器在JVM中作用就是存放当前线程接下来要执行的字节码指令,分支,循环,跳转,异常处理信息,为了在CPU时间片轮换上下文之后顺利回到正确的执行位置,每条线程都需要一个独立的程序计数器,各个线程互不影响,因此JVM将此块区域设计成线程私有

    java虚拟机栈
    虚拟机栈是与线程紧密关联的一块区域,与程序计数器相似,Java虚拟机栈也是线程私有的,它的生命周期与线程相同,是在JVM运行时所创建的,在线程中,方法在执行的时候会创建一个名为栈帧的数据结构,主要存放局部变量表,操作栈,动态链接,方法出口等信息,如下图,方法的调用对应着栈帧在虚拟机中压栈和出栈的过程
    在这里插入图片描述
    每一个线程在创建的时候,JVM都会为其创建对应的虚拟机栈,虚拟机栈的大小可以通过-xss来配置,方法的调用是栈帧被压入和弹出的操作,同等的虚拟机栈如果局部变量表占用内存越少,则被压入栈帧数量就会越多

    本地方法栈
    Java中提供调用本地方法的接口,也就是C/C++程序,在线程执行过程中,经常会碰到调用JNI方法的情况,比如网络通信,文件操作等底层,JVM为本地方法划分的内存区域就是本地方法栈,这块内存区域自由度很高,完全依靠JVM厂商实现,同样也是线程私有的内存区域

    堆内存
    堆内存是JVM中最大一块内存区域,被所有的线程共享,Java运行期间创建的所有对象几乎存在此块区域里,该内存区域也是回收器重点照顾的区域,因此也称为GC堆,堆内存一般会细分为新生代和老年代,新生代更细致划分为Eden区,From Survivor区和To Survivor区

    方法区
    方法区也是被多个线程共享的区域,主要存储以及被虚拟机加载的类信息,常量,静态变量,即时编译器(JIT) 编译后的代码等数据,虽然在Java虚拟机规范中,将方法区划分为堆内存中的一个逻辑分区,但它还是经常被称为“非堆”,有时候也称为“持久代”

    Java8 元空间
    自java1.8之后,JVM内存发生了一些改变,实际上持久代内存被彻底删除,这部分区域被Meta space代替,不同的是元空间使用的本地内存

    为什么移除持久代

    • 它的大小是在启动时固定好的——很难进行调优。-XX:MaxPermSize,设置成多少好呢?
    • HotSpot的内部类型也是Java对象:它可能会在Full GC中被移动,同时它对应用不透明,且是非强类型的,难以跟踪调试,还需要存储元数据的元数据信息(meta-metadata)
    • 可以在GC不进行暂停的情况下并发地释放类数据
    • 使得原来受限于持久代的一些改进未来有可能实现

    守护线程

    守护线程是一类比较特殊的线程,一般用来处理一些后台的工作,比如JDK垃圾回收线程
    在正常情况下,若JVM中没有一个非守护线程,则JVM进行会退出,需要注意setDaemon方法必须在线程启动之前设置才有效

    守护线程具有自动结束生命周期的特性,而非守护线程不具备,比如在一个简单游戏中,其中一个线程能不断与服务器交互获取玩家最新金币和武器信息,若希望在退出客户端时候,这个线程也能立即结束,我们往往可以将这个线程设置为守护线程

    守护线程一般执行一些后台任务,如果你希望关闭某些线程的时候,或者退出JVM进程时候,这些线程能自动关闭,可以考虑用守护线程来完成这样的工作

    展开全文
  • thread提供了几个成员函数,由于构造函数之前有讲过,所以本文着重分析一下其他函数的用法。 正文 1.~thread() 析构函数,用来销毁thread对象,但是调用析构函数的前提必须是该thread对象是不可连接的,否则不...
  • 泛型:转移构造函数

    千次阅读 2004-07-09 14:11:00
    泛型:转移构造函数 Andrei Alexandrescu 我想你们知道得很清楚,创建,拷贝,并摧毁临时对象是你的C++编译器爱做的事,临时对象在内部被创建,不幸的是这极大影响到了C++程序的执行效率。实际上,临时对象是C++程序...
  • 构造函数和析构函数的特点是当创建对象时,自动执行构造函数;当销毁对象时,析构函数自动被执行。这两个函数分别是一个对象最先和最后被执行的函数,构造函数在创建对象时调用,用来初始化该对象的初始状态和取得该...
  • Object是所有javascript的基类,提供了一种创建自定义对象的简单方式,不需要程序员再定义构造函数。 主要属性: constructor-对象的构造函数 Prototype-获得累的prototype对象,static性质 主要方法: ...
  • C++11线程指南系列文章(5)--线程移动语义实现
  • C++17 线程类

    2019-09-03 23:20:28
    线程类提供线程重用方案,一次创建反复使用,支持销毁再创建。 线程类具有高灵活性,既作为独立模块,也作为线程池的线程,既可以配置单任务,也可以配置任务队列。 线程类支持配置任务队列,可以实现单线程独占...
  • Java要求,如果您在构造函数中调用this()或super(),则它必须是第一条语句。 为什么? 例如: public class MyClass { public MyCla
  • c++多线程之std::thread的使用构造函数 c++中封装了用于创建线程thread,它需要包含头文件include<thread>include <thread>include<thread>,我们来看一下thread中的几个重要接口。 构造...
  • 线程参数传递要记住一个重要的事情,传递的参数是存在新线程一个内部的转存站中,之后在函数执行的时候再传递给函数本身的。 这种机制会引发两个问题: 1.临时参数的未及时构造 void f(int i,std::string const& ...
  • (4)move构造函数移动构造,执行成功之后x失效,即x的执行信息被移动到新产生的thread对象,该对象非joinable 下面代码示例演示了各个构造函数的用法: #include #include #include void f1(int n) { for (int i ...
  • 线程传参线程调用对象在前面的示例中,我们为线程任务使用了通常的函数。实际上,我们可以使用任何可调用对象或者lambda函数,如下调用对象的例子:#include #include <thread>class MyFunctor { public: void...
  • 现在明白了,因为这两一旦被赋值,整个生命周期内都不能修改,而在执行构造函数的函数体前,所有成员变量已经被初始化了,所以不能在构造函数体内对其赋值。 -- 大型复杂系统中,如果需要生成的对象属于一个...
  • C++11线程管理基础

    2019-04-20 14:32:27
    1. 启动线程 在C++ 11中线程是在std::thread对象创建时启动。因为我们把启动线程的重心放在如何构造这个thread对象,其...// 移动构造函数 thread( thread&& other ) noexcept; //构造新的 std::thread 对...
  • 【C++】内使用多线程

    千次阅读 2020-12-25 13:55:03
    【C++】内使用多线程 std::thread std::thread 是 C++ 11 引入的新特性,其使用也非常简单。由资源抢占所引发的加锁问题,使用 mutex 互斥量对公共变量施加保护固然可以有效地保障线程安全,但是这种方式的代价也...
  • 移动特性说明 C++标准库中有很多资源占有(resource-owning)类型,比如std::ifstream,std::unique_ptr还有std::thread都是可移动...以下是std::thread线程类的移动特性的声明,支持移动构造和移动对象,但不可拷贝。...
  • Qt多线程简单总结

    千次阅读 2017-02-14 23:46:02
    其二是通过继承QThread,重载其run()函数,当前中耗时的操作在该函数体中实现,但是要注意的一个问题是,继承自QThread的构造函数,也即该的实体在原线程中,而run函数体则在新线程中执行,所以
  • Java常见的线程安全的

    千次阅读 2018-07-31 11:56:36
    通过synchronized 关键字给方法加上内置锁来实现线程安全... 原子Atomicxxx—包装线程安全  如AtomicLong,AtomicInteger等等 Atomicxxx 是通过Unsafe 的native方法实现线程安全的 BlockingQue...
  • Mutex 又称互斥量,C++ 11中与 Mutex 相关的(包括锁类型)和函数都声明在 &lt;mutex&gt; 头文件中,所以如果你需要使用 std::mutex,就必须包含 &lt;mutex&gt; 头文件。 &lt;mutex&gt; ...
  • MFC多线程的创建

    千次阅读 2016-06-16 08:57:21
    MFC多线程简介MFC多线程基础线程函数工作线程的创建用户界面线程的创建 1用户界面线程实例12用户界面线程实例2 MFC多线程的创建 1.MFC多线程简介 MFC对多线程进行了一层简单的封装,在Visual C++中每...
  • Qt 多线程编程的 QThread (详细)

    千次阅读 2020-07-16 23:30:08
    本文结构如下: 概述 优雅的开始我们的多线程编程之旅 我们该把耗时代码放在哪里?... 安全退出线程必备函数:wait() 线程间的礼让行为 线程的中断标志位 为每个线程提供独立数据 附:所有函数...
  • 前言今天我们来研究一下Util包下的ArrayList,及其相关的线程安全实现,具体包括Vector、CopyOnWriteArrayList和集合工具Collections提供的synchronizedList。首先我们知道ArrayList是非线程安全的,而在同一...
  • Android中线程通讯Handler

    千次阅读 2013-09-27 10:04:15
    handler是线程通讯工具。用于传递消息。它有两个队列: 1.消息队列 2.线程队列 消息队列使用sendMessage和HandleMessage的组合来发送和处理消息。 线程队列类似一段代码,或者说一个方法的委托,用户传递...
  • C++11线程对象thread

    2019-05-07 22:56:49
    文章目录C++11的线程库thread对象thread对象构造函数thread对象结束方式thread对象的其它成员函数原子变量实现线程池 C++11的线程线程类thread,提供RAII式线程的创建和销毁。创建线程时传入线程要执行的代码段...
  • C++多线程(一)thread

    万次阅读 多人点赞 2018-04-10 16:28:29
    与 C++11 多线程相关的头文件地方 C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是&lt;atomic&gt; ,&...:该头文主要声明了两个, std::atomic 和 std::atomic_flag...
  • QT 多线程程序设计

    千次阅读 2014-03-15 14:49:18
    它们分别是,一、平台无关的线程类,二、线程安全的事件投递,三、跨线程的信号-槽连接。这使得开发轻巧的多线程Qt程序更为容易,并能充分利用多处理器机器的优势。多线程编程也是一个有用的模式,它用于解决执行较...
  • c++11多线程中的std::async异步函数

    千次阅读 2019-01-10 22:12:26
    future成员函数 wait_for以及wait_until返回值类型 get函数 valid函数 wait_for函数 std::launch类型 参考博客 写在前面 如果有一个需要长时间运算的线程,需要计算出最终的有效值,但是现在不迫切需要这...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 71,819
精华内容 28,727
关键字:

线程类的移动构造函数