std::thread和std::mutex

std::thread

std::thread用来创建一个线程:

#include <thread>

void threadFun(int temp)
{
	int i = 0;
}

int main()
{
	std::thread t1(threadFun, 100);
	t1.join();
	//t1.detach();
	return 0;
}

创建了一个名为t1的线程,调用join方法阻塞等待线程退出,也可以调用detach使线程处于游离态,参考Linux多线程,可以观察到,必须调用这两个方法中的一个,不然会报错:
在这里插入图片描述
看看thread析构函数:

~thread() noexcept {
    if (joinable()) {
        _STD terminate();
    }
}
    
_NODISCARD bool joinable() const noexcept {
    return _Thr._Id != 0;
}
    
void join() {
    if (!joinable()) {
        _Throw_Cpp_error(_INVALID_ARGUMENT);
    }

    if (_Thr._Id == _Thrd_id()) {
        _Throw_Cpp_error(_RESOURCE_DEADLOCK_WOULD_OCCUR);
    }

    if (_Thrd_join(_Thr, nullptr) != _Thrd_success) {
        _Throw_Cpp_error(_NO_SUCH_PROCESS);
    }

    _Thr = {};
}

void detach() {
    if (!joinable()) {
        _Throw_Cpp_error(_INVALID_ARGUMENT);
    }

    _Check_C_return(_Thrd_detach(_Thr));
    _Thr = {};
}

当thread对象被创建时,会给线程分配一个线程id(不为0),join和detach函数每次执行时会将_Thr对象置空,也就是将线程id置为0,当thread对象析构时,调用joinable函数发现线程id不为0就会terminate终止程序。

std::mutex

有关mutext相关知识可以参考Linux线程同步

mutex

有lock,try_lock,unlock三个方法。

recursive_mutex

有lock,try_lock,unlock三个方法。
它是可递归使用的互斥量,mutext对同一线程多次加锁会导致死锁,而recursive_mutex可以被同一个线程多次加锁而不会导致死锁。

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex rmtx;  // 定义一个可递归使用的互斥量对象

void printMessage(const std::string& message, int count) {
    std::lock_guard<std::recursive_mutex> lock(rmtx);  // 创建一个锁定互斥量的对象
    if (count > 0) {
        std::cout << message << " (" << count << ")" << std::endl;
        printMessage(message, count - 1);  // 递归调用
    }
}

int main() {
    std::thread t1(printMessage, "Hello", 3);

    t1.join();

    return 0;
}

printMessage方法中递归调用会重复加锁,递归退出会重复解锁。
需要注意的是,尽管std::recursive_mutex允许同一个线程多次加锁,但是相应地也必须多次解锁,每次解锁都要和加锁操作配对使用。否则,如果解锁次数多于加锁次数,那么仍然会导致死锁的问题

lock_guard

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;  // 定义一个互斥量对象

void printMessage(const std::string& message) {
    std::lock_guard<std::mutex> lock(mtx);  // 创建一个锁定互斥量的对象
    std::cout << message << std::endl;
    // 在作用域结束时,std::lock_guard对象会自动调用析构函数进行解锁操作
}

int main() {
    std::thread t1(printMessage, "Hello");
    std::thread t2(printMessage, "World");

    t1.join();
    t2.join();

    return 0;
}

lock_guard使用了RAII思想(资源获取就是初始化),构造函数里加锁,析构函数里面解锁,使用起来比较方便。

unique_lock

在C++中,std::unique_lock和std::lock_guard都是用来管理std::mutex的RAII(Resource Acquisition Is Initialization)类,它们都可以确保在作用域结束时正确释放锁,从而避免了忘记手动释放锁而导致的死锁或其他并发问题。

但是,std::unique_lock比std::lock_guard更加灵活。主要区别在于:

1、所有权:
std::lock_guard在构造时即锁定互斥量,并在作用域结束时自动解锁。它没有提供显式的解锁操作。
std::unique_lock在构造时可以选择是否锁定互斥量,而且在作用域内可以多次锁定和解锁互斥量。

2、条件变量支持:
std::unique_lock可以与std::condition_variable一起使用,因为它可以在不同的时间点上锁定和解锁互斥量。这对于实现复杂的同步操作非常有用,因为可以根据条件灵活地等待或唤醒线程。
std::lock_guard不支持条件变量,因为它没有提供显式的解锁操作。

因此,如果需要在某个作用域内多次锁定和解锁互斥量,或者需要与条件变量一起使用,则应该选择std::unique_lock。而如果只是简单地需要在作用域内锁定互斥量,且不涉及条件变量的情况,则std::lock_guard更加简洁并且安全。

condition_variable

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;  // 定义一个互斥量对象
std::condition_variable cv;  // 定义一个条件变量对象
bool isReady = false;  // 全局变量,用于表示条件是否满足

void waitForSignal() {
    std::unique_lock<std::mutex> lock(mtx);  // 创建一个锁定互斥量的对象

    // 阻塞等待条件变量isReady为true
    cv.wait(lock, [] { return isReady; });

    // 条件满足后继续执行
    std::cout << "Received the signal" << std::endl;
}

void sendSignal() {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 延迟2秒钟,模拟耗时操作

    {
        std::lock_guard<std::mutex> lock(mtx);  // 创建一个锁定互斥量的对象
        isReady = true;  // 设置条件为true
    }
    cv.notify_one();  // 唤醒一个等待线程
}

int main() {
    std::thread t1(waitForSignal);
    std::thread t2(sendSignal);

    t1.join();
    t2.join();

    return 0;
}

条件变量可以用来控制当一个线程满足某种条件是,唤醒(notify_one)等待(wait)在条件变量上的其他线程

timed_mutex

#include <iostream>
#include <thread>
#include <mutex>

std::timed_mutex mtx;  // 定义一个 timed_mutex 对象

void doWork() {
    std::cout << "Thread " << std::this_thread::get_id() << " trying to lock" << std::endl;
    if (mtx.try_lock_for(std::chrono::seconds(2))) {
        std::cout << "Thread " << std::this_thread::get_id() << " locked successfully" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(3));  // 模拟持有锁并进行一些操作
        mtx.unlock();  // 解锁
        std::cout << "Thread " << std::this_thread::get_id() << " unlocked" << std::endl;
    }
    else {
        std::cout << "Thread " << std::this_thread::get_id() << " failed to lock" << std::endl;
    }
}

int main() {
    std::thread t1(doWork);
    std::thread t2(doWork);

    t1.join();
    t2.join();

    return 0;
}

在上述示例中,定义了一个 std::timed_mutex 对象 mtx。doWork() 函数尝试在 2 秒的超时时间内获取互斥量的锁,如果成功获取锁,则持有锁并进行一些操作,然后解锁。如果在超时时间内无法获得锁,则放弃锁的获取。

std::timed_mutex 的主要方法是 try_lock_for(),该方法用于尝试在指定的时间段内获取互斥量的锁。如果在超时时间内成功获取锁,则返回 true,否则返回 false。

std::timed_mutex 的使用场景通常是在需要获取锁时,希望在一定时间内等待并放弃获取锁的线程,以避免长时间阻塞。它提供了一种更灵活的方式来控制线程对共享资源的访问。

recursive_timed_mutex

#include <iostream>
#include <chrono>
#include <mutex>
#include <thread>

std::recursive_timed_mutex mtx;  // 定义一个可递归加锁的互斥量对象

void recursiveFunc(int count)
{
    std::unique_lock<std::recursive_timed_mutex> lock(mtx, std::defer_lock);  // 创建 unique_lock 对象,但不立即锁定互斥量
    
    if (lock.try_lock_for(std::chrono::milliseconds(100))) {  // 尝试锁定互斥量,等待最多100毫秒
        std::cout << "Thread " << count << " acquired the lock." << std::endl;
        
        // 递归调用,多次获取锁
        if (count > 0) {
            recursiveFunc(count - 1);
        }
        
        lock.unlock();  // 解锁互斥量
        std::cout << "Thread " << count << " released the lock." << std::endl;
    } else {
        std::cout << "Thread " << count << " failed to acquire the lock within the timeout." << std::endl;
    }
}

int main()
{
    std::thread t1(recursiveFunc, 3);
    
    t1.join();
    
    return 0;
}

在上述示例中,定义了一个 std::recursive_timed_mutex 对象 mtx。在 recursiveFunc() 函数内部,通过创建一个 std::unique_lock 对象 lock 并使用 std::defer_lock 参数延迟锁定互斥量。然后,使用 try_lock_for() 函数尝试在最多 100 毫秒的时间内锁定互斥量。如果成功获取锁,则打印一条获取锁成功的消息,并递归调用 recursiveFunc() 函数来实现多次加锁。在每次加锁完成后,需要手动解锁。

总之,std::recursive_timed_mutex 类型提供了可递归加锁的互斥量机制,并且支持超时功能。它可以在某些场景下更方便地处理线程间的竞争和互斥问题。需要注意的是,在使用递归互斥量时需要小心避免出现死锁情况。

线程池案例

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <functional>
#include <future>

class ThreadPool {
public:
	ThreadPool(int numThreads) : stop(false) {
		for (int i = 0; i < numThreads; ++i) {
			workers.emplace_back([this] {
				while (true) {
					std::function<void()> task;
					{
						std::unique_lock<std::mutex> lock(queueMutex);
						condition.wait(lock, [this] { return stop || !tasks.empty(); });
						if (stop && tasks.empty()) {
							return;
						}
						task = std::move(tasks.front());
						tasks.pop();
					}
					task();
				}
				});
		}
	}

	template<class F, class... Args>
	std::future<typename std::result_of<F(Args...)>::type> enqueue(F&& f, Args&&... args) {
		using ReturnType = typename std::result_of<F(Args...)>::type;
		auto task = std::make_shared<std::packaged_task<ReturnType()>>(
			std::bind(std::forward<F>(f), std::forward<Args>(args)...)
		);
		std::future<ReturnType> result = task->get_future();
		{
			std::unique_lock<std::mutex> lock(queueMutex);
			if (stop) {
				throw std::runtime_error("enqueue on stopped ThreadPool");
			}
			tasks.emplace([task] { (*task)(); });
		}
		condition.notify_one();
		return result;
	}

	~ThreadPool()
	{
		{
			std::unique_lock<std::mutex> lock(queueMutex);
			stop = true;
		}
		condition.notify_all();
		for (std::thread& worker : workers) {
			worker.join();
		}
	}

private:
	std::vector<std::thread> workers;
	std::queue<std::function<void()>> tasks;
	std::mutex queueMutex;
	std::condition_variable condition;
	bool stop;
};

int main() {
	ThreadPool pool(4);

	std::vector<std::future<int>> results;

	for (int i = 0; i < 8; ++i) {
		results.emplace_back(pool.enqueue([i] {
			std::cout << "Thread " << std::this_thread::get_id() << " processing task " << i << std::endl;
			std::this_thread::sleep_for(std::chrono::seconds(1));
			return i * i;
			}));
	}

	for (auto& result : results) {
		std::cout << "Result: " << result.get() << std::endl;
	}

	return 0;
}

通过unique_lock和condition_variable实现,为什么要用unique_lock实现呢,因为当一个空闲线程获取到 unique_lock 并在条件变量上等待时,它会释放锁,这样其他的空闲线程就可以获得锁并在条件变量上等待新的任务到来,这种机制确保了在任务队列为空时,所有的空闲线程都能够等待新的任务到来,而不会造成资源的浪费。一旦有新的任务被添加到任务队列中,等待的线程会被唤醒,竞争获取锁并继续执行任务。

条件变量案例

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

class NumberPrinter {
public:
    NumberPrinter(int n) : maxNumber(n) {}

    void printOdd() {
        for (int i = 1; i <= maxNumber; i += 2) {
            std::unique_lock<std::mutex> lock(mtx);
            cv.wait(lock, [this] { return isOddTurn; });
            std::cout << "Odd: " << i << std::endl;
            isOddTurn = false;
            cv.notify_one();
        }
    }

    void printEven() {
        for (int i = 2; i <= maxNumber; i += 2) {
            std::unique_lock<std::mutex> lock(mtx);
            cv.wait(lock, [this] { return !isOddTurn; });
            std::cout << "Even: " << i << std::endl;
            isOddTurn = true;
            cv.notify_one();
        }
    }

private:
    int maxNumber;
    std::mutex mtx;
    std::condition_variable cv;
    bool isOddTurn = true;
};

int main() {
    NumberPrinter printer(10);

    std::thread t1(&NumberPrinter::printOdd, &printer);
    std::thread t2(&NumberPrinter::printEven, &printer);

    t1.join();
    t2.join();

    return 0;
}

两个线程,一个线程输出奇数,一个线程输出偶数,依次执行,通过条件变量控制执行顺序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

vegetablesssss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值