• 线程的概念及使用


    C++11 Thread线程库的使用

    传统的C++(C++11标准之前)中并没有引入线程这个概念,在C++11出来之前,如果我们想要在C++中实现多线程,需要借助操作系统平台提供的API,比如Linux的,或者windows下的 。
    本文详细介绍C++11 线程库的基本使用,包括如何创建线程、启动线程、等待线程完成、如何分离线程。

    多线程理解视频动画

    1.线程的概念及使用

    线程:进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。
    进程:进程就是运行中的程序

    • 线程内核对象。操作系统用它来管理线程,存放线程统计信息。
    • 线程堆栈,用于维护线程在执行代码时,需要的所有函数参数和局部变量。
    • 线程的最大数量取决于于CPU的核心数

    线程安全:不论运行多少次,如果多线程程序每一次运行的结果都跟单线程运行的结果是始终如一的,那么表名你的线程是安全的。

    线程函数:默认情况下我们所写的代码都是只有一个线程的,而这个线程的入口函数是main() 函数, 这是系统默认的。而我们创建的另一个线程也需要一个函数来进入, 这个函数叫做线程函数。

    在这里插入图片描述
    示例1:

    
    //多线程好处
    
    //任务分解:耗时的操作,任务分解,实时响应
    //数据分解:充分利用多核CPU处理数据
    //数据流分解:读写分离,解耦合设计
    
    
    
    
    
    //linux lpthread
    
    //要创建线程,我们需要一个可调用的函数或函数对象,作为线程的入口点。
    //在C++11中我们可以使用函数指针、函数对象或lambda表达式来实现。创建线程的基本语法如下!
    
    
    //C++11 Thread线程库的基本使用
    //创建线程 启动线程 等待线程 完成线程分离
    //要创建线程,我们需要一个可调用的函数或函数对象,作为线程的入口点
    
    
    
    #include
    #include
    void printHelloWorld()
    {
    
    	std::cout << "Hello World!" << std::endl;
    
    
    }
    
    
    int main()
    {
    	//1.创建线程
    	std::thread thread1(printHelloWorld);
    
    
    	return 0;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    如果是在单线程下,启动线程后,主程序不会等待子线程执行完函数。所以会报错。
    在这里插入图片描述

    1.1 如果是在单线程下,启动线程后,主程序不会等待子线程执行完函数。想让主程序等待子线程执行完毕。这里使用join()函数

    #include
    #include 
    void printHelloWorld()
    {
    
    	std::cout << "Hello World!" << std::endl;
    
    }
    
    
    int main()
    {
    	//1.创建线程
    	std::thread thread1(printHelloWorld);
    	thread1.join();
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.2 给线程函数传递参数

    #include
    #include 
    #include
    void printHelloWorld(std::string msg)
    {
    
    	//std::cout << "Hello World!" << std::endl;
    	std::cout << msg << std::endl;
    }
    
    
    int main()
    {
    	//1.创建线程
    	std::thread thread1(printHelloWorld, "Hello World!");
    	thread1.join();
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.3 分离线程detach(),主线程执行完毕,子线程在后台持续运行

    #include 
    #include 
    #include 
    void printHelloWorld(std::string msg)
    {
    
    	//std::cout << "Hello World!" << std::endl;
    	std::cout << msg << std::endl;
    }
    
    
    int main()
    {
    	//1.创建线程
    	std::thread thread1(printHelloWorld, "Hello World!");
    	//thread1.join();//等待线程执行完毕后再继续往下执行
    	thread1.detach();//分离线程,主线程执行完毕,子线程在后台持续运行
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    1.4 判断线程是否可以调用join()或者detach()

    #include 
    #include 
    #include 
    void printHelloWorld(std::string msg)
    {
    
    	//std::cout << "Hello World!" << std::endl;
    	std::cout << msg << std::endl;
    }
    
    
    int main()
    {
    	//1.创建线程
    	std::thread thread1(printHelloWorld, "Hello World!");
    	//thread1.join();//等待线程执行完毕后再继续往下执行
    	//thread1.detach();//分离线程,主线程执行完毕,子线程在后台持续运行
    
    	bool idJoin = thread1.joinable();
    	if (idJoin)
    	{
    		thread1.join();//等待线程执行完毕后再继续往下执行
    
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    如果说我们对一个不能够调用join()或者detach()的线程进行强行调用,程序报错systerm_error

    1.5 判断join()其实是个阻塞函数

    #include 
    #include 
    #include 
    void printHelloWorld(std::string msg)
    {
    
    	//std::cout << "Hello World!" << std::endl;
    	//std::cout << msg << std::endl;
    	for (int i = 0; i < 10000; i++)
    		std::cout << i << std::endl;
    }
    
    
    int main()
    {
    	//1.创建线程
    	std::thread thread1(printHelloWorld, "Hello World!");
    	//thread1.join();//等待线程执行完毕后再继续往下执行
    	//thread1.detach();//分离线程,主线程执行完毕,子线程在后台持续运行
    
    	bool idJoin = thread1.joinable();
    	if (idJoin)
    	{
    		thread1.join();//等待线程执行完毕后再继续往下执行
    
    	}
    	std::cout << "over" << std::endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述

    2.线程函数中的数据未定义错误

    2.1传递临时变量的问题

    2.1.1 传引用

    #include
    #include
    
    void foo(int &x)
    {
    
    	x = x + 1;
    
    }
    int main()
    {
    	std::thread  t(foo, 1);//传递临时变量
    	t.join();
    
    
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    修改为

    #include
    #include
    
    void foo(int &x)
    {
    
    	x = x + 1;
    
    }
    int main()
    {
    
    	int a = 1;
    	
    	std::thread  t(foo, std::ref(a));//传递临时变量
    	t.join();
    
    	std::cout << "a=" << a << std::endl;
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    2.1.2 传指针

    #include
    #include
    
    void foo(int *x)
    {
    
    	*x = *x + 100;
    
    }
    int main()
    {
    
    	int a = 1;
    	
    	std::thread  t(foo, &a);//传递临时变量
    	t.join();
    
    	std::cout << "a=" << a << std::endl;
    	return 0;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    2.2 传递指针或引用指向局部变量的问题:

    #include
    #include
    std::thread t;
    int a = 1;
    void foo(int *x)
    {
    
    	*x = *x + 1;
    	std::cout << "*x=" << *x << std::endl;
    }
    void test()
    {
    	
    	//int a = 1;
    	t = std::thread(foo, &a);
    	
    	
    
    }
    
    int main()
    {
    
    	test();
    	
    	t.join();
    
    	//std::cout << "a=" <
    	std::cout << "over" << std::endl;
    	return 0;
    
    
    }
    
    

    2.3 传递指针或引用指向已释放的内存的问题

    #include 
    #include 
    void foo(int& x) 
    {
    	std::cout << x << std::endl; // 访问已经被释放的内存
    }
    int main() 
    {
    	int* ptr = new int(1);
    	std::thread t(foo, *ptr); // 传递已经释放的内存
    	delete ptr;
    	t.join();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    2.4 类成员函数作为入口函数,类对象被提前释放

    #include 
    #include 
    
    class MyClass 
    {
    public:
    	void func() 
    	
    	{
    		std::cout << "Thread " << std::this_thread::get_id()
    			<< " started" << std::endl;
    		// do some work
    		std::cout << "Thread " << std::this_thread::get_id()
    			<< " finished" << std::endl;
    	}
    };
    
    int main() 
    {
    	MyClass obj;
    	std::thread t(&MyClass::func, &obj);
    	// obj 被提前销毁了,会导致未定义的行为
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述
    上面的代码中,在创建线程之后,obj 对象立即被销毁了,这会导致在线程执行时无法访问 obj 对象,可能会导致程序崩溃或者产生未定义的行为。

    为了避免这个问题,可以使用 std::shared_ptr 来管理类对象的生命周期,确保在线程执行期间对象不会被销毁。具体来说,可以在创建线程之前,将类对象的指针封装在一个 std::shared_ptr 对象中,并将其作为参数传递给线程。这样,在线程执行期间,即使类对象的所有者释放了其所有权,std::shared_ptr 仍然会保持对象的生命周期,直到线程结束。

    以下是使用 std::shared_ptr 修复上面错误的示例:

    #include 
    #include 
    #include
    class MyClass 
    {
    public:
    	void func() 
    	
    	{
    		std::cout << "Thread " << std::this_thread::get_id()
    			<< " started" << std::endl;
    		// do some work
    		std::cout << "Thread " << std::this_thread::get_id()
    			<< " finished" << std::endl;
    	}
    };
    
    int main() 
    {
    
    	std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
    	
    	std::thread t(&MyClass::func, obj);
    	t.join();
    	// obj 被提前销毁了,会导致未定义的行为
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    2.5 入口函数为类的私有成员函数

    //5.入口函数为类的私有成员函数
    
    #include 
    #include 
    
    class MyClass 
    {
    private:
    	friend void myThreadFunc(MyClass* obj);
    	void privateFunc() 
    	{
    		std::cout << "Thread "
    			<< std::this_thread::get_id() << " privateFunc" << std::endl;
    	}
    };
    
    void myThreadFunc(MyClass* obj) 
    {
    	obj->privateFunc();
    }
    
    int main() {
    	MyClass obj;
    	std::thread thread_1(myThreadFunc, &obj);
    	thread_1.join();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

  • 相关阅读:
    Spark 优化 (二) --------- Spark 数据倾斜
    MyBatis(三、注解开发)
    博客系统中的加盐算法
    适用于小型企业的远程控制软件分享!
    Groovy XML JSON
    C++_AVL树
    Linux权限管理
    SpringBoot SpringBoot 开发实用篇 4 数据层解决方案 4.14 ES 索引操作
    骨传导耳机是怎么传声的?骨传导耳机到底有哪些好处?
    痞子衡嵌入式:MCUXpresso IDE下将源码制作成Lib库方法及其与IAR,MDK差异
  • 原文地址:https://blog.csdn.net/weixin_40933653/article/details/133634932