C++11 简单手撕多线程编程

如何使用线程库

std::thread 创建线程

thread1.join(); 阻塞主线程

thread1.detach(); 线程分离

cpp 复制代码
#include<iostream>
#include<thread>

void helloworld(std::string msg) {
	for (int i = 0; i < 10000; i++)
	{
		std::cout << i << std::endl;
	}
	//std::cout << msg << std::endl;
	return;
}

int main() {
	//1. 创建线程
	std::thread thread1(helloworld, "hello Tread");
	//thread1.join();
	//thread1.detach();
	bool isJoin = thread1.joinable();
	if (isJoin) 
	{
		thread1.join();
	};
	std::cout << "over" << std::endl;
}

线程中常见的数据传递错误

临时变量

cpp 复制代码
#include <iostream>
#include <thread>
void foo(int& x) {
    x += 1;
}
int main() {
    int x = 1; // 将变量复制到一个持久的对象中
    std::thread t(foo, std::ref(x)); // 将变量的引用传递给线程,不可以传递临时变量
    t.join();
    return 0;
}

传递指针,需要将指针或引用指向堆上的变量,或使用std::shared_ptr等智能指针来管理对象的生命周期。

cpp 复制代码
#include <iostream>
#include <thread>

void foo(int* ptr) {
    std::cout << *ptr << std::endl;
    delete ptr; // 在使用完指针后,需要手动释放内存
}
int main() {
    int* ptr = new int(1); // 在堆上分配一个整数变量
    std::thread t(foo, ptr); // 将指针传递给线程
    t.join();
    return 0;
}
cpp 复制代码
#include <iostream>
#include <thread>
#include <memory> // 引入shared_ptr的头文件

void foo(std::shared_ptr<int> ptr) {
    std::cout << *ptr << std::endl;
}

int main() {
    std::shared_ptr<int> ptr = std::make_shared<int>(1); // 使用make_shared分配内存
    std::thread t(foo, ptr); // 传递shared_ptr到线程
    t.join();
    return 0;
}

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

cpp 复制代码
#include <iostream>
#include <thread>

void foo(int& x) {
    std::cout << x << std::endl;
}
int main() {
    int x = 1;
    std::thread t(foo, std::ref(x)); 
    // 将变量的引用传递给线程,在线程函数执行期间,变量`x`的生命周期是有效的。
    t.join();
    return 0;
}

类对象被提前释放

cpp 复制代码
#include <iostream>
#include <thread>
#include <memory>

class MyClass {
public:
    void func() {
        std::cout << "Thread " << std::this_thread::get_id() 
        << " started" << std::endl;
        
        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);
    //避免obj被提前销毁,导致未定义的行为
    t.join();
    return 0;
}

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

cpp 复制代码
#include <iostream>
#include <thread>

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;
}

将 myThreadFunc 定义为 MyClass 类的友元函数,并在函数中调用 privateFunc 函数。在创建线程时,需要将类对象的指针作为参数传递给线程。

多线程数据共享

互斥锁 (mutex)

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

int shared_data = 0;
std::mutex mtx;

void func(int n) {
    for (int i = 0; i < 1000; ++i) {
        mtx.lock();
        shared_data++;
        std::cout << "Thread " << n << " increment shared_data to " << shared_data << std::endl;
        mtx.unlock();
    }
}
int main() {
    std::thread t1(func, 1);
    std::thread t2(func, 2);

    t1.join();
    t2.join();
    std::cout << "Final shared_data = " << shared_data << std::endl;
    return 0;
}

互斥量死锁

如果 T1 获取了 mtx1 的所有权,但是无法获取 mtx2 的所有权,而 T2 获取了 mtx2 的所有权,但是无法获取 mtx1 的所有权,两个线程互相等待对方释放互斥量,就会导致死锁。解决办法,需要资源顺序化,或者通过一次性尝试锁定所有指定的互斥锁,确保要么成功获取所有锁,要么释放已持有的锁并重新尝试,从而避免了死锁。

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx1, mtx2;

void func1() {
    // 锁定 mtx2,然后再锁定 mtx1
    mtx2.lock();
    std::cout << "Thread 1 locked mutex 2" << std::endl;

    mtx1.lock();
    std::cout << "Thread 1 locked mutex 1" << std::endl;

    // 解锁 mtx1 和 mtx2
    mtx1.unlock();
    std::cout << "Thread 1 unlocked mutex 1" << std::endl;

    mtx2.unlock();
    std::cout << "Thread 1 unlocked mutex 2" << std::endl;
}

void func2() {
    // 锁定 mtx2,然后再锁定 mtx1
    mtx2.lock();
    std::cout << "Thread 2 locked mutex 2" << std::endl;

    mtx1.lock();
    std::cout << "Thread 2 locked mutex 1" << std::endl;

    // 解锁 mtx1 和 mtx2
    mtx1.unlock();
    std::cout << "Thread 2 unlocked mutex 1" << std::endl;

    mtx2.unlock();
    std::cout << "Thread 2 unlocked mutex 2" << std::endl;
}

int main() {
    // 启动两个线程,分别执行 func1 和 func2
    std::thread t1(func1);
    std::thread t2(func2);

    // 等待两个线程结束
    t1.join();
    t2.join();

    return 0;
}

lock_guard 与 std::unique_lock

使用 std::lock 和 std::lock_guard 或 std::unique_lock,它们能确保同时尝试锁定多个互斥量,并且不会死锁。

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx1, mtx2;

void func1() {
    std::lock(mtx1, mtx2); // 同时锁定两个互斥量,避免死锁
    std::lock_guard<std::mutex> lk1(mtx1, std::adopt_lock); // adopt_lock表示互斥量已经被锁定
    std::lock_guard<std::mutex> lk2(mtx2, std::adopt_lock);
    
    std::cout << "Thread 1 locked mutexes 1 and 2" << std::endl;
    // 自动解锁
}

void func2() {
    std::lock(mtx1, mtx2); // 同时锁定两个互斥量
    std::lock_guard<std::mutex> lk1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lk2(mtx2, std::adopt_lock);
    
    std::cout << "Thread 2 locked mutexes 1 and 2" << std::endl;
    // 自动解锁
}

int main() {
    std::thread t1(func1);
    std::thread t2(func2);
    t1.join();
    t2.join();
    return 0;
}

std::unique_lock 提供了以下几个成员函数:

  • lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。
  • try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 true。
  • try_lock_for(const std::chrono::duration<Rep, Period>& rel_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
  • try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。
  • unlock():对互斥量进行解锁操作。

call_once

单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑线程安全的问题。

全局只需要一个该类的对象,

下面是一个简单的单例模式的实现:

cpp 复制代码
#include<iostream>
#include<thread>
#include<mutex>
#include<string>

class Log {
	Log() {};
public:
	Log(const Log& log) = delete;
	Log& operator = (const Log &log) = delete;

	static Log& GetInstance() {
		//static Log log;
		//return log;

		static Log *log = nullptr;

		if (!log) log = new Log;

		return *log;
	}

	void PrintLog(std::string msg) {
		std::cout<<__TIME__<< msg << std::endl;
	};
};

int main() {
	Log::GetInstance().PrintLog("error");
}

使用 std::call_once 可以确保单例实例只会被创建一次,从而避免了多个对象被创建的问题。此外,使用 std::unique_ptr 可以确保单例实例被正确地释放,避免了内存泄漏的问题。

cpp 复制代码
#include<iostream>
#include<string>
#include <thread>
#include <mutex>
class Log
{
public:
	static Log& GetInstance()
	{
		//static Log log; //饿汉模式
		//return log;

		std::call_once(once, initfunc);

		return *log;
	}

	static void initfunc()
	{
		if (!log)
		{
			log = new Log;
		}
	}

	void PrintLog(std::string msg)
	{
		std::cout << __TIME__ << " " << msg << std::endl;
	}

private:
	Log() {}
	~Log() {}
	Log(const Log&) = delete;
	Log& operator=(const Log&) = delete;

	//静态成员必须类外初始化
	static Log* log;
	static std::once_flag once;
};

Log* Log::log = nullptr;

std::once_flag Log::once; void print_error()
{
	Log::GetInstance().PrintLog("error");
}

int main()
{
	std::thread t1(print_error);
	std::thread t2(print_error);
	t1.join();
	t2.join();
	return 0;
}

condition_variable

生产者与消费者模型

跨平台线程池

异步并发

原子操作

相关推荐
白-胖-子32 分钟前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-成绩排序ABCDE
c++·算法·蓝桥杯·等考·13级
香蕉你个不拿拿^2 小时前
【C++】中Vector与List的比较
开发语言·c++
Dream_Snowar2 小时前
全国高校计算机能力挑战赛区域赛2019C++选择题题解
开发语言·c++
90wunch3 小时前
WinDefender Weaker
c++·安全
mahuifa3 小时前
C++(Qt)软件调试---内存泄漏分析工具MTuner (25)
c++·qt·内存泄漏·软件调试·mtuner
电子系的小欣3 小时前
图论基本术语
c++·算法·图论
电子系的小欣3 小时前
1 图的搜索 & 奇偶剪枝
c++·算法·剪枝
石牌桥网管3 小时前
用正则表达式检查是IP否为内网地址
java·c++·golang·正则表达式·php·c
我爱工作&工作love我4 小时前
1436:数列分段II -整型二分
c++·算法
IU宝4 小时前
类和对象——拷贝构造函数,赋值运算符重载(C++)
开发语言·c++