首页 > 编程语言 >在 C++ 中,实现线程同步主要有以下几种常见方法

在 C++ 中,实现线程同步主要有以下几种常见方法

时间:2024-11-09 18:10:19浏览次数:4  
标签:std 信号量 同步 resource C++ 共享资源 线程 shared

  1. 互斥锁(Mutex)
    原理:
    互斥锁用于保护共享资源,确保在同一时刻只有一个线程能够访问被保护的资源。当一个线程想要访问共享资源时,它首先尝试获取互斥锁。如果锁已经被其他线程持有,那么该线程会被阻塞,直到锁被释放。一旦获取到锁,线程就可以安全地访问共享资源,访问结束后再释放锁,以便其他线程能够获取锁并访问资源。
    示例代码:
    cpp
    复制
#include <iostream>
#include <thread>
#include <mutex>

// 定义一个互斥锁
std::mutex mutex_f;

// 共享资源,这里是一个简单的整数变量
int shared_resource = 0;

// 线程函数,用于对共享资源进行操作
void increment_resource() {
    // 获取互斥锁
    std::lock_guard<std::mutex> guard(mutex_f);

    // 对共享资源进行操作
    shared_resource++;

    // 释放互斥锁(由lock_guard的析构函数自动完成)
}

int main() {
    // 创建两个线程
    std::thread t1(increment_resource);
    std::thread t2(increment_resource);

    // 等待线程完成
    t1.join();
    t2.join();

    // 输出共享资源的值
    std::cout << "Shared resource value: " << shared_resource << std::endl;

    return 0;
}

在上述示例中,通过std::lock_guard来自动管理互斥锁的获取和释放,确保在increment_resource函数中对共享资源shared_resource的操作是线程安全的。
2. 条件变量(Condition Variable)
原理:
条件变量通常与互斥锁一起使用,用于让一个线程等待某个特定条件的满足。线程首先获取互斥锁,然后检查条件是否满足。如果条件不满足,线程会释放互斥锁并进入等待状态,直到被其他线程通过通知机制唤醒。被唤醒后,线程会再次获取互斥锁并重新检查条件是否满足,只有当条件满足时才会继续执行后续操作。
示例代码:
cpp
复制

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

// 定义互斥锁和条件变量
std::mutex mutex_g;
std::condition_variable condition_g;

// 共享资源,这里是一个简单的整数变量
int shared_resource_g = 0;

// 用于标识条件是否满足的标志
bool condition_met = false;

// 线程函数,用于增加共享资源的值并在满足条件时通知其他线程
void increment_and_notify() {
    // 获取互斥锁
    std::lock_guard<std::mutex> guard(mutex_g);

    // 对共享资源进行操作
    shared_resource_g++;

    // 当共享资源的值达到一定程度时,设置条件满足标志并通知其他线程
    if (shared_resource_g >= 5) {
        condition_met = true;
        condition_g.notify_all();
    }
}

// 线程函数,用于等待条件满足
void wait_for_condition() {
    // 获取互斥锁
    std::unique_lock<std::mutex> lock(mutex_g);

    // 等待条件满足,当条件不满足时释放互斥锁并进入等待状态
    condition_g.wait(lock, []{ return condition_met; });

    // 条件满足后,输出相关信息
    std::cout << "Condition met, shared resource value: " << shared_resource_g << std::endl;
}

int main() {
    // 创建两个线程
    std::thread t1(increment_and_notify);
    std::thread t2(wait_for_condition);

    // 等待线程完成
    t1.join();
    t2.join();

    return 0;
}

在上述示例中,increment_and_notify线程负责增加共享资源的值,并在满足特定条件(shared_resource_g >= 5)时通知其他线程。wait_for_condition线程则通过条件变量等待条件满足,在条件满足后输出相关信息。
3. 信号量(Semaphore)
原理:
信号量是一种用于控制对共享资源访问的计数器。它有一个初始值,表示可用资源的数量。当一个线程想要访问共享资源时,它会先尝试获取信号量。如果信号量的值大于 0,线程就可以获取信号量(即将信号量的值减 1)并访问共享资源。访问结束后,线程会释放信号量(即将信号量的值加 1),以便其他线程能够获取信号量并访问资源。如果信号量的值为 0,线程会被阻塞,直到有其他线程释放信号量。
示例代码:
cpp
复制

#include <iostream>
#include <thread>
#include <semaphore.h>

// 定义一个信号量,并设置初始值为3,表示有3个可用资源
sem_t semaphore_h;

// 共享资源,这里简单地用一个整数变量表示
int shared_resource_h = 0;

// 线程函数,用于获取信号量、访问共享资源并释放信号量
void access_shared_resource() {
    // 获取信号量
    sem_wait(&semaphore_h);

    // 对共享资源进行操作
    shared_resource_h++;

    // 释放信号量
    sem_post(&semaphore_h);

    // 输出相关信息
    std::cout << "Accessed shared resource, current value: " << shared_resource_h << std::endl;
}

int main() {
    // 初始化信号量
    sem_init(&semaphore_h, 0, 3);

    // 创建三个线程
    std::thread t1(access_shared_resource);
    std::thread t2(access_shared_resource);
    std::thread t3(access_shared_resource);

    // 等待线程完成
    t1.join();
    t2.join();
    t3.join();

    // 销毁信号量
    sem_destroy(&semaphore_h);

    return 0;
}

在上述示例中,通过sem_wait获取信号量,sem_post释放信号量,实现了对共享资源shared_resource_h的线程安全访问。
4. 读写锁(Read-Write Lock)
原理:
读写锁用于区分对共享资源的读操作和写操作。多个线程可以同时进行读操作,因为读操作不会改变共享资源的状态。但是,当有一个线程进行写操作时,其他线程无论是读还是写都不能同时进行,必须等待写操作完成。读写锁通过维护读锁和写锁的状态来实现这种区分。
示例代码:
cpp
复制

#include <iostream>
#include <thread>
#include <shared_mutex>

// 定义一个读写锁
std::shared_mutex read_write_lock_i;

// 共享资源,这里是一个简单的整数变量
int shared_resource_i = 0;

// 线程函数,用于进行读操作
void read_shared_resource() {
    // 获取读锁
    std::shared_lock<std::shared_mutex> read_lock(read_write_lock_i);

    // 对共享资源进行读操作
    std::cout << "Read shared resource value: " << shared_resource_i << std::endl;

    // 释放读锁(由shared_lock的析构函数自动完成)
}

// 线程函数,用于进行写操作
void write_shared_resource() {
    // 获取写锁
    std::unique_lock<std::shared_mutex> write_lock(read_write_lock_i);

    // 对共享资源进行写操作
    shared_resource_i++;

    // 释放写锁(由unique_lock的析构函数自动完成)
}

int main() {
    // 创建三个线程进行读操作,一个线程进行写操作
    std::thread t1(read_shared_resource);
    std::thread t2(read_shared_resource);
    std::thread t3(read_shared_resource);
    std::thread t4(write_shared_resource);

    // 等待线程完成
    t1.join();
    t2.join();
    t3.join();
    t4.join();

    return 0;
}

在上述示例中,通过std::shared_lock获取读锁进行读操作,通过std::unique_lock获取写锁进行写操作,实现了对共享资源shared_resource_i的读写操作的线程安全管理。
5. 原子类型(Atomic Types)
原理:
原子类型是 C++11 及以后引入的一种数据类型,它允许对其进行原子操作,即这些操作在多线程环境下是不可分割的,要么完整执行,要么不执行,不存在中间状态被其他线程看到的情况。常见的原子类型有std::atomic、std::atomic等。通过使用原子类型来定义共享资源,可以避免使用传统的同步机制(如互斥锁等)来实现线程安全。
示例代码:
cpp
复制

#include <iostream>
#include <thread>
#include <atomic>

// 定义一个原子类型的整数变量
std::atomic<int> atomic_shared_resource_j(0);

// 线程函数,用于对原子类型的共享资源进行操作
void increment_atomic_resource() {
    // 对原子类型的共享资源进行操作
    atomic_shared_resource_j++;

    // 输出相关信息
    std::cout << "Atomic shared resource value: " << atomic_shared_resource_j.load() << std::endl;
}

int main() {
    // 创建两个线程
    std::thread t1(increment_atomic_resource);
    std::thread t2(increment_atomic_resource);

    // 等待线程完成
    t1.join();
    t2.join();

    return 0;
}

在上述示例中,通过使用std::atomic定义共享资源atomic_shared_resource_j,并直接对其进行原子操作(如++操作),实现了线程安全的资源管理。

标签:std,信号量,同步,resource,C++,共享资源,线程,shared
From: https://www.cnblogs.com/DesertCactus/p/18537064

相关文章

  • 在C++中,条件变量的等待操作是如何实现的?
    在C++中,条件变量的等待操作主要通过std::condition_variable类来实现,其等待操作涉及到与互斥锁的配合使用,以下是详细的实现过程:包含必要的头文件首先需要包含<condition_variable>和头文件,因为条件变量std::condition_variable的使用需要与互斥锁(如std::mutex)协同工作,同时还......
  • 静态变量在多线程环境下的初始化是线程安全的吗?
    C++11之前的情况在C++11之前,标准并没有对静态变量在多线程环境下的初始化提供线程安全保证。这意味着如果多个线程同时访问一个未初始化的静态变量,可能会导致初始化过程多次执行或者出现数据竞争等问题。例如,假设有一个函数包含一个静态局部变量:intgetValue(){static......
  • PHP、Java、Python、C、C++ 这几种编程语言都各有什么特点或优点?
    相信每一个计算机科班出身的同学或许都有这样的经历:在大三的某一天,仿佛打通了全身筋脉一般把三年的所学:“数电里的与非门——[计算机体系结构]——汇编语言——C语言——C++语言——Java语言”。所有知识全部串联了起来。所有这些语言的出现都仿佛都有了必然性和追根溯源的历史......
  • devc++配置opengl库
    由于VisualStudio太占内存,所以用老古董devc++配图形学的环境。用到的文件下载链接Step1:建项目首先打开dev点文件--新建--项目--Multimedia--OpenGLc++/c都行(我这里用的c++)名称最好用英文,然后确定,保存的地方也最好没有中文路径Step2:添加库文件找到DEV-C++的安装目录(右键......
  • PHP中的多线程与并发编程:如何提高处理能力
    在现代的网络应用中,处理能力是评估系统性能的一个关键指标。随着用户数量的激增和数据量的增加,单线程程序往往难以满足高并发的需求。虽然PHP本身是单线程的,但通过合理的多线程与并发编程技巧,我们依然可以提高处理能力,提升程序的响应速度和稳定性。理解PHP的并发模型是至关重要的......
  • 线程池创建方式
    线程池创建方式  一、方式一:通过ThreadPoolExecutor构造函数来创建(推荐)  方式二:通过Executor框架的工具类Executors来创建。  Executors工具类提供的创建线程池的方法如下图所示:  可以看出,通过Executors工具类可以创建多种类型的线程池,包括:  1. Fixed......
  • Redis的线程模型
    Redis的单线程模型详解        Redis的“单线程”模型主要指的是其主线程,这个主线程负责从客户端接收请求、解析命令、处理数据和返回响应。为了深入了解Redis单线程的具体工作流程,我们可以将其分为以下几个步骤:接收客户端请求Redis的主线程会通过网络接口接......
  • C++入门(C语言语法改进篇)
    目录C++第一个程序命名空间namespace的价值namespace定义命名空间的使用C++输入输出缺省参数全缺省参数半缺省参数函数重载参数类型不同参数个数不同C++第一个程序C++的文件名称后缀为.cpp,C++包含了C语言的大部分语法,所以在.cpp文件里面我们依然可以使用C语言编程......
  • C++输出奇特的三角形
    题目描述请根据下列规律输出奇特的图形,是一个被*包围了的@三角形n=3**@***@@@*@@@@@输入输入一个整数(3<=n<10)输出输出n行的图形样例输入 3样例输出 **@***@@@*@@@@@#include<iostream>usingnamespacestd;intmain(){ intn,j=1,t=0; cin>>n;......
  • 深入计算机语言之C++:模板初阶
     ......