当前位置: 首页 > article >正文

Linux——线程首尾(各个小知识及理解)

Linux——信号量和(环形队列消费者模型)-CSDN博客


文章目录

目录

文章目录

前言

一、线程安全的单例模式是什么?

饿汉式

懒汉式(双重检查锁定)

二、读写锁

1.相关函数

2、理解基于读写锁的读者写者问题 

问题描述

读写锁的作用

代码实现

代码解释

优势

注意事项

总结


前言

pthread_cp_1_21 · MFF的库/linux - 码云 - 开源中国 (gitee.com)

环形队列,线程池及线程的封装。


一、线程安全的单例模式是什么?

单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在多线程环境下,实现线程安全的单例模式有多种方式,以下是几种常见的实现方式:

饿汉式

  • 特点:在类加载时就立即创建单例实例,不存在线程安全问题,因为类的加载是由 JVM 保证线程安全的。
  • 思路:在程序启动时就创建单例对象,利用静态对象的初始化机制保证线程安全。

代码

#include <iostream>

class Singleton {
private:
    // 构造函数设为私有,防止外部实例化
    Singleton() {}
    // 拷贝构造函数和赋值运算符设为删除,防止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态成员变量,在程序启动时就初始化
    static Singleton instance;

public:
    // 公共静态方法,用于获取单例实例
    static Singleton& getInstance() {
        return instance;
    }
};

// 静态成员变量的初始化
Singleton Singleton::instance;

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    std::cout << (&s1 == &s2) << std::endl;  // 输出1,表示是同一个实例
    return 0;
}

Singleton类的构造函数是私有的,避免外部创建对象。instance是静态成员变量,在程序启动时就会初始化,且只会初始化一次,保证了单例的唯一性。getInstance方法返回这个静态实例的引用。 

懒汉式(双重检查锁定)

  • 思路:在第一次使用时才创建对象,通过双重检查锁定和std::mutex来保证线程安全。
#include <iostream>
#include <mutex>

class Singleton {
private:
    // 构造函数设为私有,防止外部实例化
    Singleton() {}
    // 拷贝构造函数和赋值运算符设为删除,防止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态成员变量,用于存储单例实例
    static Singleton* instance;
    // 静态互斥锁,用于线程同步
    static std::mutex mutex_;

public:
    // 公共静态方法,用于获取单例实例
    static Singleton& getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mutex_);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return *instance;
    }
};

// 静态成员变量的初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    std::cout << (&s1 == &s2) << std::endl;  // 输出1,表示是同一个实例
    return 0;
}
  • getInstance方法中定义了一个静态局部变量instance,C++11 标准保证静态局部变量的初始化是线程安全的,只会初始化一次,从而实现了单例模式。

二、读写锁

在 Linux 系统里,线程读写锁(Read-Write Lock)是一种同步机制。它把对共享资源的访问操作分为读操作和写操作,并且允许多个线程同时进行读操作,但在有线程进行写操作时,其他线程既不能读也不能写。这样的机制可以提升并发性能,适用于读多写少的场景。

1.相关函数

在 Linux 中使用读写锁主要会用到以下几个函数,这些函数都在<pthread.h>头文件中定义:

初始化读写锁:pthread_rwlock_init函数用于初始化读写锁。

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

销毁读写锁:pthread_rwlock_destroy函数用于销毁读写锁。

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加读锁:pthread_rwlock_rdlock函数用于获取读锁。若有其他线程持有写锁,调用线程会阻塞,直至写锁被释放。
 

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

尝试加读锁:pthread_rwlock_tryrdlock函数用于尝试获取读锁。若锁不可用,不会阻塞,而是立即返回错误。

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

 加写锁:pthread_rwlock_wrlock函数用于获取写锁。若有其他线程持有读锁或写锁,调用线程会阻塞。

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

尝试加写锁:pthread_rwlock_trywrlock函数用于尝试获取写锁。若锁不可用,不会阻塞,直接返回错误。

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

解锁:pthread_rwlock_unlock函数用于释放读锁或写锁。

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

2、理解基于读写锁的读者写者问题 

读者 - 写者问题是一个经典的并发编程问题,它描述了多线程或多进程对共享资源进行访问的场景,其中一些线程(读者)仅读取资源,而另一些线程(写者)会修改资源。读写锁是解决这个问题的一种有效手段,下面将详细阐述基于读写锁的读者 - 写者问题。

问题描述

读者 - 写者问题里,存在多个读者和多个写者线程,它们共同访问一个共享资源。规则如下:

  • 读者:多个读者可以同时读取共享资源,因为读操作不会改变资源的状态,所以不会产生冲突。
  • 写者:写者在对共享资源进行写操作时必须独占该资源,也就是说,当有写者在进行写操作时,不允许其他读者或写者访问资源;而且在有读者进行读操作时,也不允许写者进行写操作。

读写锁的作用

读写锁(Read - Write Lock)是一种特殊的锁机制,它能很好地解决读者 - 写者问题。读写锁将对资源的访问权限分为读权限和写权限:

  • 读锁:允许多个线程同时持有读锁,这意味着多个读者可以同时读取共享资源,从而提高并发性能。
  • 写锁:写锁是排他的,当一个线程持有写锁时,其他线程既不能获取读锁也不能获取写锁,确保了写操作的原子性和数据的一致性。

代码实现

以下是一个使用 C++ 和 POSIX 线程库实现的基于读写锁的读者 - 写者问题的示例代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

// 定义读写锁
pthread_rwlock_t rwlock;
// 共享资源
int sharedData = 0;

// 读者线程函数
void* reader(void* arg) {
    // 获取读锁
    pthread_rwlock_rdlock(&rwlock);
    std::cout << "Reader " << *(int*)arg << " is reading. Data: " << sharedData << std::endl;
    // 模拟读操作耗时
    sleep(1);
    // 释放读锁
    pthread_rwlock_unlock(&rwlock);
    return nullptr;
}

// 写者线程函数
void* writer(void* arg) {
    // 获取写锁
    pthread_rwlock_wrlock(&rwlock);
    sharedData++;
    std::cout << "Writer " << *(int*)arg << " is writing. New data: " << sharedData << std::endl;
    // 模拟写操作耗时
    sleep(1);
    // 释放写锁
    pthread_rwlock_unlock(&rwlock);
    return nullptr;
}

int main() {
    // 初始化读写锁
    pthread_rwlock_init(&rwlock, nullptr);

    // 创建读者和写者线程
    pthread_t readers[3], writers[2];
    int readerIds[3] = {1, 2, 3};
    int writerIds[2] = {1, 2};

    for (int i = 0; i < 3; ++i) {
        pthread_create(&readers[i], nullptr, reader, &readerIds[i]);
    }
    for (int i = 0; i < 2; ++i) {
        pthread_create(&writers[i], nullptr, writer, &writerIds[i]);
    }

    // 等待所有线程结束
    for (int i = 0; i < 3; ++i) {
        pthread_join(readers[i], nullptr);
    }
    for (int i = 0; i < 2; ++i) {
        pthread_join(writers[i], nullptr);
    }

    // 销毁读写锁
    pthread_rwlock_destroy(&rwlock);

    return 0;
}

代码解释

  1. 初始化读写锁:在main函数中,调用pthread_rwlock_init函数对读写锁rwlock进行初始化。
  2. 读者线程:reader函数首先调用pthread_rwlock_rdlock获取读锁,然后读取共享资源sharedData,模拟读操作耗时后,调用pthread_rwlock_unlock释放读锁。
  3. 写者线程:writer函数先调用pthread_rwlock_wrlock获取写锁,对共享资源sharedData进行写操作,模拟写操作耗时后,调用pthread_rwlock_unlock释放写锁。
  4. 线程创建与等待:在main函数中创建多个读者和写者线程,并使用pthread_join函数等待所有线程执行完毕。
  5. 销毁读写锁:所有线程执行完毕后,调用pthread_rwlock_destroy函数销毁读写锁。

优势

  • 提高并发性能:多个读者可以同时读取共享资源,减少了线程间的等待时间,提升了系统的整体性能。
  • 保证数据一致性:写者在进行写操作时独占资源,避免了数据竞争和不一致的问题。

注意事项

  • 死锁问题:虽然读写锁本身能避免很多并发问题,但在复杂的程序中,如果使用不当,仍可能出现死锁情况。
  • 锁的粒度:需要合理控制锁的粒度,避免锁的范围过大影响性能,或者锁的范围过小导致数据不一致。

 

总结

 


http://www.kler.cn/a/521292.html

相关文章:

  • 自然语言处理(NLP)入门:基础概念与应用场景
  • 智能码二维码赋能智慧工厂建设
  • 126周日复盘 (166)本周回顾
  • 毛桃病害分割数据集labelme格式212张6类别
  • [文献阅读] Unsupervised Deep Embedding for Clustering Analysis (DEC)(pytorch复现)
  • 网络安全 | F5-Attack Signatures-Set详解
  • Day38:移除列表中的元素
  • python3+TensorFlow 2.x(五)CNN
  • JS高阶 - day04
  • ubuntu取消定时锁定
  • 学院失物招领 app 的设计与实现
  • 计算机图形学实验练习(实验1.2-4.1AND补充实验12)
  • 【阅读笔记】基于整数+分数微分的清晰度评价算子
  • 数据的秘密:如何用大数据分析挖掘商业价值
  • Ubuntu 24.04 安装 NVIDIA Container Toolkit 全指南:让Docker拥抱GPU
  • for...in 和 Object.keys().forEach的区别
  • GO语言 链表(单向链表
  • 接口管理文档Yapi的安装与配置
  • 华硕笔记本装win10哪个版本好用分析_华硕笔记本装win10专业版图文教程
  • 无所不搜,吾爱制造