多线程 - 线程参数与数据
多线程 - 线程参数与数据
线程传参
临时对象(值传递)
引用传递(错误写法)
#include <iostream>
#include <thread>			// 线程头文件
using namespace std;
void myprint(const int &i, char *pmybuf)	// 带参数的线程的初始函数
{
    cout << i << endl;						// 输出:1 				【调试】&i = 0x0072d9e
    cout << pmybuf << endl;					// 输出:this is a test!	【调试】&pmybuff = 0x004ffb64
}
    
int main()
{
    int mvar = 1;							// 【调试】&mvar = 0x0022fa58 {1}
    int &mvary = mvar;						// 【调试】&mvary = 0x0022fa58 {1}
    char mybuf[] = "this is a test!";		// 【调试】&mybuff = 0x004ffb64
    thread mythread(myprint, mvar, mybuf);	// 注意这里传参会有点不一样
    mythread.detach();
    cout << "主线程" << endl;
    return 0;
}存在问题
- i,看似引用传参,但实际上是值传递,指向的不是同一地址!
 - pmybuf,是真正的引用传参
 - 不推荐用引用、detach子线程中绝对不可以用指针
 
解决方案
- 简单类型传值,传字符串时参数为
const string &pmybuf,打印pmybuf.c_str() - 这里要加
const的原因:略 
- 简单类型传值,传字符串时参数为
 
隐式转换传递(错误写法)
#include <iostream>
#include <thread>			// 线程头文件
using namespace std;
void myprint(const int i, const string &pmybuf)	// 带参数的线程的初始函数
{
    cout << i << endl;						// 输出:1 				【调试】&i = 0x0072d9e
    cout << pmybuf.c_str() << endl;			// 输出:this is a test!	【调试】&pmybuff = 不同
}
int main(){
    int mvar = 1;							// 【调试】&mvar = 0x0022fa58 {1}
    int &mvary = mvar;						// 【调试】&mvary = 0x0022fa58 {1}
    char mybuf[] = "this is a test!";		// 【调试】&mybuff = 0x004ffb64
    thread mythread(myprint, mvar, mybuf);	// 注意这里传参会有点不一样
    mythread.detach();
    cout << "主线程" << endl;
    return 0;
}存在问题
- mybuf什么时候转换成string?有可能主线程结束后,mybuf被回收后才转换成string,此时会出问题
 
解决方案
- 直接转换,不要隐式转换。因为隐式转换的过程会在子线程中
 
显示类型转换为临时变量(正确写法)
#include <iostream>
#include <thread>			// 线程头文件
using namespace std;
void myprint(const int i, const string &pmybuf)
{
    cout << i << endl;
    cout << pmybuf.c_str() << endl;					// 【调试】00C0D298
}
int main(){
    int mvar = 1;
    int &mvary = mvar;
    char mybuf[] = "this is a test!";
    thread mythread(myprint, mvar, string(mybuf));	// 直接转换,不要隐式转换	【调试】0079F6D0
    mythread.detach();
    cout << "主线程" << endl;
    return 0;
}- 隐式转换和显式转换区别(可以用线程id来调试一下并验证) 
- 隐式转换 
- string类的活动:一次类型转换构造函数(传参时 子线程)、一次析构函数(子线程)
 
 - 显示转换 
- string类的活动:进行一次类型转换构造函数(主线程)、一次复制构造函数(传参时 主线程)、两次析构函数(一次主线程、一次子线程)
 
 - 区别原因 
- 显示转换写法中,能确保类型转换后立刻调用复制构造函数。因为这里的复制构造函数不是传参引发的,而是Thread构造函数内部的一个行为
 - 但隐式转换写法中,类型转换构造函数不是Thread完成的,和普通函数一样不能确保传参后立刻调用
 
 
 - 隐式转换 
 - 结论 
传递int这种简单类型参数,值传递
不建议使用detach
 
类对象(ref函数传递)
拷贝构造写法(地址不同)
#include <iostream>#include <thread>			// 线程头文件
using namespace std;void myprint(const int i, const string &pmybuf){
    cout << "《子线程》" << endl
        <<"《线程id》"<<std::this_thread:get_id()<<endl;
}
class A						// 要传递的类对象
{
public:
    mutable int m_i;
    A(int a):m_i(a){
        cout<<"【构造函数】"<<endl
            <<"【地址】"<<this<<endl
            <<"【线程id】"<<std::this_thread:get_id()<<endl;
    }
    A(const A &a):m_i{
        cout<<"【复制构造函数】"<<endl
            <<"【地址】"<<this<<endl
            <<"【线程id】"<<std::this_thread:get_id()<<endl;
    }
    ~A(){
        cout<<"【析构函数】"<<endl
            <<"【地址】"<<this<<endl 
            <<"【线程id】"<<std::this_thread:get_id()<<endl;
    }
}
int main(){
    A aObj(10);		// 类对象
    thread mythread(myprint, aObj);				// 直接传
    mythread.join();
    cout << "《主线程》" << endl
        <<"《线程id》"<<std::this_thread:get_id()<<endl;
    return 0;
}- 调试结果 
- 类对象一次构造函数(主线程)、一次拷贝构造函数(子线程)、两次析构函数(主线程、子线程各一次),两次构造的对象地址不同
 - 这种写法可以用detach
 
 
ref写法(地址相同)
int main(){
    A aObj(10);		// 类对象
    thread mythread(myprint, std::ref(aObj));	// 【修改】ref,真正的引用传递,令得主线程和子线程公用同一个地址的同一个对象
    mythread.join();
    cout << "《主线程》" << endl
        <<"《线程id》"<<std::this_thread:get_id()<<endl;
    return 0;
}调试结果
- 主线程和子线程公用同一个地址的同一个对象
 - 类对象一次构造函数(主线程)、一次析构情况(主线程)
 - 这种写法不能用detach,会出错
 
深层原理
传递类对象时,避免隐式类型转换。创建线程时构建临时对象,并用引用来接
线程初始函数用引用来接,这里的引用并没有失效。如果用值传递的话,会有三次构造函数!一次默认构造、两次复制构造
这里其中的一次复制构造函数不是传参造成的,而是新建线程时的默认行为:thread构造函数内部会有一个构造tuple
选用
- 类对象全部使用ref()就挺好的
 
智能指针(move函数传递)
错误写法
int main(){
    unique_ptr<int> ap(new int(100));			// 智能指针
    thread mythread(myprint, ap);				// 直接传,会报错。报错原因:unique不支持拷贝构造函数
    mythread.join();
    cout << "《主线程》" << endl
        <<"《线程id》"<<std::this_thread:get_id()<<endl;
    return 0;
}- 报错原因 
- unique不支持拷贝构造函数
 
 
移动语义方案
int main(){    
    unique_ptr<int> ap(new int(100));			// 智能指针
    thread mythread(myprint, std::move(ap));	// 【修改】移动语义方案(本质是所有权转移)
    mythread.join();
    cout << "《主线程》" << endl
        <<"《线程id》"<<std::this_thread:get_id()<<endl;
    return 0;
}- 调试结果 
- 主线程和子线程的智能指针指向同一块地址
 - 这种写法不能用detach,会出错
 
 - 危险性 
- 线程都有独立堆栈空间,主线程的智能指针指向主线程堆栈空间,在std::move后主线程智能指针为空
 - 但通过new分配的内存依然在主线程中,子线程指针指向的内存在主线程中
 - 当主线程结束后,内存回收,子线程智能指针指向的是主线程地址,会导致程序错乱
 - 总结:也就是说当主线程和子线程存在共享内存数据时,绝对不能用detach()
 
 
共享参数
只读数据
直接使用即可,加上const更安全
#include <iostream>
#include <thread>			// 线程头文件
using namespace std;
vector <int> g_v = {1,2,3};	// 【共享数据】不传参也可被线程直接使用
void myprint(const int i){
    cout << "《子线程》" << endl
        <<"《线程id》"<<std::this_thread:get_id()<<endl;
    cout << g_v[0] << endl;	// 【共享数据】直接使用
}
int main(){
    vector <thread> mythreads;
    for(int i=0; i<10; i++)
    {
        mythreads.push_back(thread(myprint, i));
    }
    for(auto iter=mythreads.begin(); iter!=mythreads.end(); iter++)
    {
        iter->join();
    }
    cout << "《主线程》" << endl 
        <<"《线程id》"<<std::this_thread:get_id()<<endl;
    return 0;
}可读可写(互斥量加锁)
这种情况下,代码写得有问题会很容易导致程序崩溃
- 关键处理:读的时候不能写、写的时候不能读/写
 - 深层原因:写的操作不原子性,可能正在写的时候任务切换,发生问题
 - 解决方案:用互斥量加线程锁,将共享数据的操作锁住
 
应用举例
- 比如火车有10个售票窗口(或者电影院网上订座),当订座时不能冲突
 
链接到当前文件 0
没有文件链接到当前文件