线程

一、创建线程

1、通过函数来初始化线程

void MyPrint()
{
    cout << "自定义线程" << endl;
    cout << "自定义线程结束" << endl;
}

int main()
{
    std::thread mythread(MyPrint);  // (1)thread是一个类,接受的参数是一个可调用对象
    // 创建了线程,线程入口为MyPrint(),并且该线程开始执行
    
    mythread.join();   // (2)join() 加入/汇合,阻塞主线程,当子线程执行完,主线程再继续往下走
    // 让主线程等待子线程执行完毕,完后子线程和主线程汇合,然后主线程再往下走
    // 一般情况下,主线程需要等子线程执行完毕之后,自己才能退出

    mythread.detach();
    // detach() :传统多线程程序,主线程要等待子线程执行完毕,然后最后退出
    // 主线程不必等子线程运行结束,
    // 一旦detach()后,子线程与主线程失去关联,此时子线程会驻留在后台运行,
    // 这个子线程相当于被C++运行时刻接管,当子线程执行完毕后,由运行时库清理该线程相关的资源
    // detach()使线程失去自己的控制,而且detach()后无法在join()回来,所以一般不推荐使用

    if (mythread.joinable())
    {
        mythread.join();
    }
    // joinable():
    // 没有join()过得,返回true
    // join() 过,或者detach()过,返回false

    cout << "主线程" << endl;
    system("pause");
    return 0;
}

2、通过类对象来初始化线程

class Element
{
public:
    void operator ()()  // 重载() 运算符,后面一个()表示不带参
    {   
        // 重载类的()运算符,作为线程的入口函数
        cout << "类对象作为可调用对象的线程入口" << endl;
    }
};

// 使用
Element ele;
thread myThread(ele);  // 用类对象作为调用对象来初始化线程
myThread.join();

3、通过lambda表达是来初始化线程

// myLambda 可写在 main() 函数中
auto myLambda = []
{
    // Lambda表达式作为线程入口
    cout << "Lambda表达式作为线程入口" << endl;
}

thread myThread(myLambda);  // 用类对象作为调用对象来初始化线程
myThread.join();

 二、线程相关知识

 1、线程id

// 获取线程id
thread::id  std::this_thread::get_id();

// 在参数的类的构造函数和析构函数中 获取当前线程的id, 可以知道参数对象是在哪个线程中被构造出来的,可以用于调试

2、自定义类型引用作为线程参数

如果传递的参数是普通类,则不是真正的引用,要用std::ref() 包装一下参数,才是真正的引用

class Element{
public:
    mutable int m_id;   // mutable 即使是const 常量也可以修改该成员变量

    Element(int i) :m_id(i) {}
};

// 说明:
// 1、线程函数中的参数用的是自定义类,即使参数是引用类型,还是还会执行一次拷贝构造函数
// 在主线程中传递的是显式构造的类,拷贝构造函数会在主线程中执行
// 在主线程中传递的是隐式构造的类,例如直接传递1,则只会在子线程中执行构造函数
// 2、如果参数传递的是值,则会执行两次拷贝构造函数,一次在主线程中,一次在子线程中
// 3、传递的参数用std::ref(ele) 包一下,则不会再执行拷贝构造函数
void MyPrint(const Element& ele)   
{
    ele.m_id = 10;

}

int main()
{
    Element ele(1);
    std::thread mythread(MyPrint, ele);  // MyPrint函数的参数直接跟在后面

    // 如果想传递引用到子线程中,并接受子线程对参数的修改,则需要用std::ref
    // 而且不会再执行拷贝构造函数
    // 用syd::ref 传参之后,线程中的引用参数可以不加const了,不然必须要加const
    // void MyPrint(Element& ele)   
    std::thread mythread(MyPrint, std::ref(ele));  // std::ref 传递的是真正的引用

    mythread.join();  

    cout << "主线程" << endl;
    system("pause");
    return 0;
}

3、智能指针作为线程参数

    // 智能指针作为线程参数
    void MyPrint(unique_ptr<int> pElement); // 线程函数

    // 主函数调用
    unique_ptr<int> pInt(new int(100));
    std::thread mythread(MyPrint, std::move(pInt));  // 用std::move 将一个独占式指针作为参数传递

4、成员函数作为线程函数

class Element{
public:
    mutable int m_id;   // mutable 即使是const 常量也可以修改该成员变量

    Element(int i) :m_id(i) {}

public:
    void threadFun(int num)  // 类的任意成员函数作为线程入口
    {
    }

    void operator () (int num)  // 重载() 作为线程入口
    {
    }
};

// 成员函数作为线程函数的例子
Element ele(1);
std::thread mythread(&Element::threadFun, ele, 10);  // 函数名,对象名,函数参数
std::thread mythread(ele, 10);   // 调用Element 类的() 运算符作为线程入口函数
std::thread mythread(std::ref(ele), 10);  // std::ref 使用ele的引用
mythread.join();

 

posted @ 2020-06-22 23:18  min_zhi  阅读(171)  评论(0编辑  收藏  举报