⛺️不负时光,不负己✈️
引言
相信大家在Linux系统编程中都接触过线程创建和退出的相关系统调用,这些系统调用是Linux环境下的一套线程设计方案。但是这种设计方案仅限于Linux环境下使用,其缺点就是可移植性差。所以C++设计了thread库,该库可以适用于任何平台下,从根本上解决了可移植性差的问题。
thread
要使用 std::thread,首先需要包含头文件
#include<thread>
创建线程
可以通过 std::thread 类的构造函数来创建一个线程。构造函数接受一个可调用对象(如函数指针、函数对象、lambda 表达式等)作为参数。线程创建好之后,会自动运行所绑定的函数。
void threadFunction()
{
cout << "hello 函数指针" << endl;
}
int main()
{
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方法」
线程等待
主线程要等待新线程全部运行完毕,主线程才能退出,所以要进行线程分离。
thread t(绑定函数)
t.join()
线程分离
线程分离是指将一个线程从主线程中分离出来,使其能够运行。当一个线程被设置为分离状态时,它结束时系统会自动回收其资源,而不需要主线程使用join函数来等待其结束并手动回收资源。
thread t(绑定函数)
t.detach()
传递参数给线程函数
线程函数可以接受参数,这些参数在创建线程时传递给 std::thread 的构造函数。
来看如下示例
#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环境下,有这样几个内核暴露出来的系统调用接口:
#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常见用法
定义和初始化
#include<mutex>
#include<iostream>
int main()
{
std::mutex mtx;
}
加锁和解锁
访问被保护的资源「临界资源」,必须先获得锁的拥有权。线程必须先锁定mutex,这可以通过调用lock()成员函数实现。一旦完成资源访问,线程应该调用unlock()来释放mutex。
mtx.lock();
mtx.unlock();
使用std::lock_guard
为了避免忘记解锁或在异常发生时未能解锁,C++提供了std::lock_guard。它是一个简单的RAII(Resource Acquisition Is Initialization)包装器,它在构造时锁定mutex,在析构时自动解锁。
#include <mutex>
std::mutex mtx;
void threadSafeFunction() {
std::lock_guard<std::mutex> lock(mtx);
}
使用std::unique_lock
std::unique_lock提供了比std::lock_guard更多的灵活性。除了自动管理mutex的锁定和解锁外,它还允许延迟锁定、提前解锁、重新锁定等操作。
#include <mutex>
std::mutex mtx;
void threadSafeFunction() {
std::unique_lock<std::mutex> lock(mtx);
}
假设现在我们要设计一个抢票的程序:有三个窗口和100张票,我们应该如何设计呢?
我们可以这样设计:
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环境中,内核暴露给用户一些接口,用于环境变量相关的操作,如下:
#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一起使用。
#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() wait\n";
cond_var.wait(lock);
std::cout << "worker_thread() is processing data\n";
}
int main() {
std::thread worker(worker_thread);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
std::cout << "main() notify_one\n";
cond_var.notify_one();
worker.join();
std::cout << "main() end\n";
return 0;
}
2. std::condition_variable::notify_one 和 std::condition_variable::notify_all
这两个函数用于唤醒等待条件变量的线程。notify_one唤醒一个等待的线程,而notify_all唤醒所有等待的线程。
范例(使用notify_all):
#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
这个函数用于在一定时间内等待条件变量被唤醒。如果指定时间内条件变量没有被唤醒,则返回超时状态。
#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;
}
生产消费模型
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())
{
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();
}