c++ 线程安全与线程管理

发布于:2024-07-03 ⋅ 阅读:(17) ⋅ 点赞:(0)

C++11引入了标准库中的多线程支持,包括std::thread类和各种同步机制

  • 互斥锁std::mutex : 互斥锁用于保护共享资源,确保同一时间只有一个线程能够访问该资源,以防止数据竞争。

  • 条件变量std::condition_variable : 条件变量用于线程之间的通信,允许一个或多个线程等待某个条件(或者事件)的发生,而另一个线程则可以通知这些等待的线程该条件已经满足。

  • 原子操作std::atomic : 原子操作用于无锁编程,提供对基本数据类型的原子读写操作,防止数据竞争而无需显式的锁机制。

  • 信号量(Semaphore):用于控制对共享资源的访问,可以实现多个线程之间的同步和互斥。

  • 读写锁(Reader-Writer Lock):用于实现读写线程对共享数据的访问控制,允许多个读线程同时进行读操作,但只允许一个写线程进行写操作。

  • 屏障(Barrier):用于保证多个线程在某个点上同步,只有当所有线程都达到屏障点时才能继续执行。

  • 事件(Event):用于实现线程间的通信和同步,一个线程等待某个事件的发生,而另一个线程触发该事件。

  • 互斥量递归锁(Recursive Mutex):与互斥锁类似,但允许同一个线程多次获得同一个锁,避免死锁。

  • 读写互斥量(Read-Write Mutex):类似于读写锁,但使用互斥量来实现,可以更灵活地控制读写线程对共享数据的访问。

创建和管理线程

#include <iostream>
#include <thread>

void printMessage(const std::string& message) {
    std::cout << message << std::endl;
}

int main() {
    std::thread t1(printMessage, "Hello from thread 1");
    std::thread t2(printMessage, "Hello from thread 2");

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

    return 0;
}

使用互斥锁进行同步

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

std::mutex mtx;
/* std::lock_guard 是一个模板类,其模板参数是要管理的互斥量的类型。在这里,我们指定了 std::mutex,因为我们要管理 mtx 这个 std::mutex 对象的锁。
  lock 是 std::lock_guard 的实例对象名,它通过构造函数接收一个 std::mutex 对象 mtx,并在构造时自动锁定(即调用 mtx.lock())。
  当 lock 对象离开其作用域(通常是当前作用域结束或显式销毁),它会自动解锁互斥量(即调用 mtx.unlock()),这样确保了在离开作用域时不会忘记解锁互斥量,避免了潜在的死锁问题。*/

void printMessage(const std::string& message) {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << message << std::endl;
}

int main() {
    std::thread t1(printMessage, "Hello from thread 1");
    std::thread t2(printMessage, "Hello from thread 2");

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

    return 0;
}

使用条件变量进行线程间通信

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

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

/*std::unique_lock 提供了比 std::lock_guard 更多的灵活性,因为它允许在构造时或之后释放锁。std::condition_variable 允许一个或多个线程等待,直到另一个线程通知它们继续执行。wait 函数会自动释放 lock 并阻塞当前线程,直到满足特定条件。在这里,[] { return ready; } 是一个lambda表达式,它作为条件传递给 wait 函数。条件 ready 是一个全局变量或者类成员变量,用于表示线程是否可以继续执行。一旦条件变量 cv 接收到通知并且lambda表达式返回true(即ready为true),线程就会被唤醒并且重新获得了 lock。然后线程就会输出 "Hello from thread!"*/
void printMessage() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(printMessage);

    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();  // 通知一个等待的线程继续执行

    t.join();  // 等待线程 t 执行完毕

    return 0;
}

信号量(Semaphore):

sem_t semaphore; 这行代码定义了一个信号量变量 semaphore,它是基于 POSIX 标准的信号量类型 sem_t 的实例。

信号量(Semaphore)概述:
信号量是一种用于线程或进程之间同步和互斥的机制。它通常用于控制对共享资源的访问,确保多个线程或进程可以安全地访问共享资源,或者按照特定的顺序执行任务。

POSIX 信号量 sem_t:
在 POSIX 标准中,信号量类型 sem_t 是一种原子类型,可以用于多线程或进程之间的同步。它提供了4个基本的操作:

sem_init:初始化一个信号量。
sem_wait:等待信号量,如果信号量的值大于零,则将其减一;如果为零,则当前线程阻塞等待。
sem_post:发布(释放)信号量,将其值加一,并唤醒一个等待在该信号量上的线程。
sem_destroy:销毁信号量。释放信号量所占用的资源。

信号量常用于以下情况:

线程同步:控制多个线程的执行顺序,确保某些线程在特定条件下执行。
资源管理:限制对共享资源(如文件、内存区域等)的访问,避免数据竞争和冲突。
生产者-消费者问题:协调生产者和消费者线程的操作,确保数据安全和正确处理。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <semaphore.h>

sem_t semaphore;
/* worker 函数是线程 t 执行的入口函数。
在 worker 函数中,调用 sem_wait(&semaphore) 等待信号量。由于初始时信号量为 0,所以线程会阻塞在这里,直到主线程调用 sem_post(&semaphore) 释放信号量。*/
void worker() {
    sem_wait(&semaphore); // 等待信号量
    std::cout << "执行任务" << std::endl;
}

int main() {
    sem_init(&semaphore, 0, 0); // 通过 sem_init() 函数初始化了一个信号量 semaphore,初始值设为 0。这意味着初始时信号量处于阻塞状态,不允许通过。
    std::thread t(worker);
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 主线程休眠了 2 秒钟,模拟一些耗时操作或者等待条件满足的过程。
    // 主线程通过 sem_post() 函数发送信号量,增加信号量的值。这会解除一个等待在 semaphore 上的线程的阻塞状态。
    sem_post(&semaphore); // 发送信号量
    // 主线程等待线程 t 执行完毕后再继续执行。线程 t 在执行过程中会调用 worker 函数。
    t.join();
    // 最后,在程序结束前销毁信号量,释放资源。
    sem_destroy(&semaphore);
    return 0;
}

读写锁(Reader-Writer Lock):

#include

#include <thread>
#include <shared_mutex>
// rwMutex 是一个 std::shared_mutex 类型的对象,用于实现读写线程之间的共享访问控制。
// data 是一个整数变量,它是被多个线程共享的数据。
std::shared_mutex rwMutex;  // 共享互斥量
int data = 0;

/* reader 函数是一个读者线程的入口函数。它使用 std::shared_lock 对象 lock,这是共享互斥量 rwMutex 的共享模式锁(读锁)。
在 lock 的作用域内,多个读者线程可以同时访问共享资源 data,因为共享锁允许多个线程同时进行读取操作。
reader 函数简单地打印当前的 data 值。*/
void reader() {
    std::shared_lock<std::shared_mutex> lock(rwMutex);
    std::cout << "读取数据:" << data << std::endl;
}
/*writer 函数是一个写者线程的入口函数。它使用 std::unique_lock 对象 lock,这是共享互斥量 rwMutex 的独占模式锁(写锁)。
在 lock 的作用域内,只有一个写者线程能够修改共享资源 data,因为独占锁会阻止其他线程(包括读者和写者)进入临界区。
writer 函数简单地将 data 值递增,并打印修改后的值。*/
void writer() {
    std::unique_lock<std::shared_mutex> lock(rwMutex);
    data++;
    std::cout << "写入数据:" << data << std::endl;
}

int main() {
    std::thread readerThread1(reader);
    std::thread readerThread2(reader);
    std::thread writerThread(writer);

    readerThread1.join();
    readerThread2.join();
    writerThread.join();

    return 0;
}

屏障(Barrier):

#include <iostream>
#include <thread>
#include <barrier>

std::barrier myBarrier(3); // 创建一个屏障,需要三个线程都到达后才能继续执行
/*worker 函数是每个工作线程的入口函数。
std::this_thread::sleep_for(std::chrono::seconds(1)) 模拟了一些耗时操作,这里是线程休眠1秒钟。
std::cout << "执行任务" << std::endl; 打印了 "执行任务",表示任务执行开始。
myBarrier.arrive_and_wait(); 调用了屏障对象的 arrive_and_wait() 方法,使当前线程等待,直到所有三个线程都到达屏障。
std::cout << "任务完成" << std::endl; 打印了 "任务完成",表示任务执行结束。*/
void worker() {
    // 模拟一些操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "执行任务" << std::endl;
    myBarrier.arrive_and_wait(); // 到达屏障并等待
    std::cout << "任务完成" << std::endl;
}

int main() {
    std::thread t1(worker);
    std::thread t2(worker);
    std::thread t3(worker);

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

    return 0;
}
<< 执行任务
	执行任务
	执行任务
	任务完成
	任务完成
	任务完成

事件(Event)

事件可以用于实现线程间的通信和同步,一个线程等待某个事件的发生,而另一个线程触发该事件。

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

std::mutex mtx;
std::condition_variable cv;
bool eventOccurred = false;
/*std::unique_lock<std::mutex> lock(mtx);:使用 unique_lock 在互斥量上进行加锁。
cv.wait(lock);:在等待期间,线程会释放锁,并且会阻塞直到收到 cv.notify_one() 通知。
一旦事件发生 (eventOccurred 变为 true),线程被唤醒,输出消息并执行任务。*/

void waitForEvent() {
    std::unique_lock<std::mutex> lock(mtx);
    while (!eventOccurred) {
        cv.wait(lock);
    }
    std::cout << "事件已发生,执行任务" << std::endl;
}
/*std::lock_guard<std::mutex> lock(mtx);:使用 lock_guard 对互斥量进行短期锁定,确保修改 eventOccurred 的操作是原子的。
cv.notify_one();:通知一个正在等待的线程(如果有的话),以便它可以继续执行。
输出 "事件已触发" 的消息。*/
void triggerEvent() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        eventOccurred = true;
    }
    cv.notify_one();
    std::cout << "事件已触发" << std::endl;
}

int main() {
    std::thread t1(waitForEvent);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::thread t2(triggerEvent);

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

    return 0;
}

互斥量递归锁(Recursive Mutex)

互斥量递归锁允许同一个线程多次获得同一个锁,避免了死锁。

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

std::recursive_mutex mtx;
/*std::lock_guard<std::recursive_mutex> lock(mtx);:在函数内部使用 lock_guard 对递归互斥锁 mtx 进行加锁。因为是递归互斥锁,同一线程可以多次加锁而不会产生死锁。
if (depth > 0):递归终止条件,如果递归深度大于0,则输出当前深度并继续递归调用 recursiveFunction。*/
void recursiveFunction(int depth) {
    std::lock_guard<std::recursive_mutex> lock(mtx);
    if (depth > 0) {
        std::cout << "递归深度:" << depth << std::endl;
        recursiveFunction(depth - 1);
    }
}

int main() {
    recursiveFunction(3);

    return 0;
}

读写互斥量(Read-Write Mutex)

读写互斥量使用互斥量来实现,可以更灵活地控制读写线程对共享数据的访问。

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

std::mutex readMutex;
std::mutex writeMutex;
int data = 0;

void reader() {
    std::lock_guard<std::mutex> lock(readMutex);
    std::cout << "读取数据:" << data << std::endl;
}

void writer() {
    std::lock_guard<std::mutex> lock(writeMutex);
    data++;
    std::cout << "写入数据:" << data << std::endl;
}

int main() {
    std::thread readerThread1(reader);
    std::thread readerThread2(reader);
    std::thread writerThread(writer);

    readerThread1.join();
    readerThread2.join();
    writerThread.join();

    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到