C++:thread | condition_variable|mutex

2024-10-10 08:10:21 浏览数 (1)

引言

相信大家在Linux系统编程中都接触过线程创建和退出的相关系统调用,这些系统调用是Linux环境下的一套线程设计方案。但是这种设计方案仅限于Linux环境下使用,其缺点就是可移植性差。所以C 设计了thread库,该库可以适用于任何平台下,从根本上解决了可移植性差的问题。

thread

要使用 std::thread,首先需要包含头文件

代码语言:javascript复制
#include<thread>
创建线程

可以通过 std::thread 类的构造函数来创建一个线程。构造函数接受一个可调用对象(如函数指针、函数对象、lambda 表达式等)作为参数。线程创建好之后,会自动运行所绑定的函数。

代码语言:javascript复制
void threadFunction()
{
	cout << "hello 函数指针" << endl;
}
int main()
{
	//传入lambda表达式
	thread t1([](int x = 10) {
		
		{
			cout << "hello lambda表达式" << endl;
		}});
	//传入函数指针
	thread t2(threadFunction);
	//传入函数对象
	function<void()>func = threadFunction;
	thread t3(func);
	//进行线程等待
	t1.join();
	t2.join();
	t3.join();
}

线程创建好之后,要进行线程等待「调用其内部的join方法」或者进行线程分离「调用其内部的detach方法」 线程等待 主线程要等待新线程全部运行完毕,主线程才能退出,所以要进行线程分离。

代码语言:javascript复制
thread t(绑定函数)
t.join()

线程分离 线程分离是指将一个线程从主线程中分离出来,使其能够独立运行。当一个线程被设置为分离状态时,它结束时系统会自动回收其资源,而不需要主线程使用join函数来等待其结束并手动回收资源。

线程被分离后,该线程和创建它的线程「例如主线程」之间任何关系,创建它的线程进行退出时,也不会检查被分离线程是否运行完成,

代码语言:javascript复制
thread t(绑定函数)
//线程分离
t.detach()
传递参数给线程函数

线程函数可以接受参数,这些参数在创建线程时传递给 std::thread 的构造函数。 来看如下示例

代码语言:javascript复制
#include <iostream>  
#include <thread>  
  
// 一个接受参数的线程函数  
void threadFunction(int x, const std::string& str) {  
    std::cout << "x = " << x << ", str = " << str << std::endl;  
}  
  
int main() {  
    int x = 42;  
    std::string str = "Hello from thread";  
  
    // 创建一个线程,传递参数  
    std::thread t(threadFunction, x, str);  
  
    // 等待线程完成  
    t.join();  
  
    return 0;  
}

mutex

在Linux环境下,有这样几个内核暴露出来的系统调用接口:

代码语言:javascript复制
 #include <pthread.h>

 int pthread_mutex_lock(pthread_mutex_t *mutex);
 int pthread_mutex_trylock(pthread_mutex_t *mutex);
 int pthread_mutex_unlock(pthread_mutex_t *mutex);

这些接口组成了锁的概念,但是由于这些接口仅可以在Linux环境下使用,可移植性较差。C 在这些系统调用接口的基础上,封装出了mutex类。 在C 中,mutex(互斥量)是一种同步机制,用于防止多个线程同时访问共享资源,从而避免数据竞争和条件竞争等问题。它是C 11标准库引入的一部分,位于头文件中。通过使用mutex,开发者可以确保在任何时刻只有一个线程能够访问特定的代码段或资源。

mutex常见用法

定义和初始化

代码语言:javascript复制
#include<mutex>
#include<iostream>
int main()
{
	std::mutex mtx;
}

加锁和解锁 访问被保护的资源「临界资源」,必须先获得锁的拥有权。线程必须先锁定mutex,这可以通过调用lock()成员函数实现。一旦完成资源访问,线程应该调用unlock()来释放mutex。

代码语言:javascript复制
mtx.lock();
//被保护的临界资源
mtx.unlock();

使用std::lock_guard 为了避免忘记解锁或在异常发生时未能解锁,C 提供了std::lock_guard。它是一个简单的RAII(Resource Acquisition Is Initialization)包装器,它在构造时锁定mutex,在析构时自动解锁。

代码语言:javascript复制
#include <mutex>  

std::mutex mtx;  

void threadSafeFunction() {  
    std::lock_guard<std::mutex> lock(mtx);  
    // 访问受保护的资源  
}

使用std::unique_lock

std::unique_lock提供了比std::lock_guard更多的灵活性。除了自动管理mutex的锁定和解锁外,它还允许延迟锁定、提前解锁、重新锁定等操作。

代码语言:javascript复制
#include <mutex>  

std::mutex mtx;  

void threadSafeFunction() {  
    std::unique_lock<std::mutex> lock(mtx);  
    // 可以在这里进行延迟锁定、提前解锁等操作  
    // 访问受保护的资源  
}

假设现在我们要设计一个抢票的程序:有三个窗口和100张票,我们应该如何设计呢? 我们可以这样设计:

代码语言:javascript复制
mutex mtx;
int ticketaCount = 100;
void threadFunction(int index)
{
	while (ticketaCount > 0)
	{
		{
			lock_guard<std::mutex> guard(mtx);
			if (ticketaCount > 0)
			{
				cout << index << " : " << ticketaCount << endl;
				ticketaCount--;
			}
			std::this_thread::sleep_for(std::chrono::milliseconds(100));
		}
	}
}
int main()
{
	vector<thread> poll;
	for (int i = 1; i <= 3; i  )
	{
		poll.push_back(thread(threadFunction, i));

	}
	for (auto &it : poll)
	{
		it.join();
	}
}

tips: 锁的力度越小越好,因为我越小发生错误的概率越低。

condition_variable:条件变量

在Linux环境中,内核暴露给用户一些接口,用于环境变量相关的操作,如下:

代码语言:javascript复制
       #include <pthread.h>

       int pthread_cond_timedwait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abstime);
       int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);

C 语言层面对其进行了封装,但其背后使用的还是不同的操作系统提供的系统调用的接口,同时也使其拥有了较强的可移植性。 以下是一些C 中std::condition_variable相关函数的使用范例: 1. std::condition_variable::wait 这个函数用于阻塞当前线程,直到条件变量被另一个线程唤醒。它通常与std::unique_lock std::mutex一起使用。

代码语言:javascript复制
#include <iostream>  
#include <thread>  
#include <mutex>  
#include <condition_variable>  
  
std::mutex m;  
std::condition_variable cond_var;  
bool ready = false;  
  
void worker_thread() {  
    std::unique_lock<std::mutex> lock(m);  
    std::cout << "worker_thread() waitn";  
    cond_var.wait(lock); // 等待条件变量被唤醒  
    std::cout << "worker_thread() is processing datan";  
}  
  
int main() {  
    std::thread worker(worker_thread);  
    std::this_thread::sleep_for(std::chrono::milliseconds(5)); // 模拟一些延迟  
    std::cout << "main() notify_onen";  
    cond_var.notify_one(); // 唤醒一个等待的线程  
    worker.join();  
    std::cout << "main() endn";  
    return 0;  
}

2. std::condition_variable::notify_one 和 std::condition_variable::notify_all 这两个函数用于唤醒等待条件变量的线程。notify_one唤醒一个等待的线程,而notify_all唤醒所有等待的线程。 范例(使用notify_all):

代码语言:javascript复制
#include <iostream>  
#include <thread>  
#include <mutex>  
#include <condition_variable>  
  
std::mutex m;  
std::condition_variable cond_var;  
bool ready = false;  
  
void print_id(int id) {  
    std::unique_lock<std::mutex> lock(m);  
    while (!ready) {  
        cond_var.wait(lock); // 等待条件变量被唤醒  
    }  
    std::cout << "thread " << id << 'n';  
}  
  
void go() {  
    std::unique_lock<std::mutex> lock(m);  
    ready = true;  
    cond_var.notify_all(); // 唤醒所有等待的线程  
}  
  
int main() {  
    std::thread threads[5];  
    for (int i = 0; i < 5;   i) {  
        threads[i] = std::thread(print_id, i);  
    }  
    std::cout << "5 threads ready to race...n";  
    go();  
    for (auto& th : threads) {  
        th.join();  
    }  
    return 0;  
}

3. std::condition_variable::wait_for 这个函数用于在一定时间内等待条件变量被唤醒。如果指定时间内条件变量没有被唤醒,则返回超时状态。

代码语言:javascript复制
#include <iostream>  
#include <thread>  
#include <mutex>  
#include <condition_variable>  
#include <chrono>  
  
std::mutex mtx;  
std::condition_variable cv;  
bool ready = false;  
  
void worker() {  
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟工作  
    {  
        std::lock_guard<std::mutex> lock(mtx);  
        ready = true;  
    }  
    cv.notify_one(); // 通知等待的线程  
}  
  
int main() {  
    std::thread t(worker);  
    std::unique_lock<std::mutex> lock(mtx);  
    if (cv.wait_for(lock, std::chrono::seconds(2), [] { return ready; })) {  
        std::cout << "Worker线程已完成工作。n";  
    } else {  
        std::cout << "等待超时。n";  
    }  
    t.join();  
    return 0;  
}

生产消费模型

代码语言:javascript复制
mutex mtx;
condition_variable cv;
class Queue
{
public:
	void put(int i)
	{
		unique_lock<std::mutex>lck(mtx);
		while (!que.empty())
		{
			//这时,生产者应通知消费者去消费,消费完了,再继续生产
			//生产者进入等待状态,要将锁给释放,并且将自身进入等待状态。
			cv.wait(lck);
		}
		que.push(i);
		//通知其他一个线程,我生产完了 你们赶快去消费去吧
		//其他线程得到该通知,就会从等待状态-->阻塞状态--->获取互斥锁才能继续之星

		cv.notify_all();
		cout << "生产者 生产:" << i << "号商品" << endl;
	}
	int get()
	{
		unique_lock<std::mutex>lck(mtx);		
		while (que.empty())
		{
			//消费线程发现:que是空的,通知生产线程。
			
			cv.wait(lck);
		}
		int q = que.front();
		que.pop();
		cv.notify_all();
		cout << "消费者 消费:" << q << "号商品" << endl;
		//通知其他生产线程进行生产:
		
		return q; 
	}
private:
	queue<int> que;
};
void Productor(Queue* que)
{
	for (int i = 0; i < 10; i  )
	{
		
		que->put(i);
		std::this_thread::sleep_for(std::chrono::seconds(2));
	}
}
void Consumer(Queue* que)
{

	for (int i = 0; i < 10; i  )
	{
		
		que->get();
		std::this_thread::sleep_for(std::chrono::seconds(2));
	}
}
int main()
{
	Queue que;
	thread productor(Productor, &que);
	thread consumer(Consumer, &que);
	productor.join();
	consumer.join();
}

0 人点赞