多线程并发编程

2023-05-16

文章目录

  • 多线程并发编程
    • 一、多线程带来的问题
      • 相关概念
    • 二、互斥
      • 1、互斥与互斥量
      • 2、申请互斥量
        • I. 静态方法申请互斥量:
        • II. 动态方法申请互斥量:
      • 3、利用互斥量加锁与解锁
      • 4、销毁互斥量
      • 5、互斥量综合应用——模拟抢票
      • 6、互斥量的原子性
      • 7、线程饥饿问题
    • 三、同步
      • 1、同步与条件变量
      • 2、条件变量的初始化
      • 3、线程等待满足条件变量
      • 4、唤醒进程
        • I. 唤醒一个等待的线程
        • II. 唤醒所有等待的线程
      • 5、条件变量的销毁
      • 6、条件变量的经典应用——生产者消费者模型
        • I. 生产者与生产者、消费者与消费者的关系
        • II. 生产者与消费者的关系
        • III. 代码实现
    • 四、线程池
      • 1、线程池的优势
      • 2、线程池的应用场景
    • 五、可重入与线程安全
      • 1、线程不安全的函数
      • 2、不可重入的函数
      • 3、可重入与线程安全的区别
      • 4、STL与线程安全
    • 六、死锁
      • 1、死锁四个必要条件
      • 2、如何避免死锁
    • 七、特殊类型的锁
      • 1、自旋锁
      • 2、读写锁(读者写者问题)
        • 读写锁接口
      • 3、乐观锁与悲观锁
        • I. 悲观锁
        • II. 乐观锁

多线程并发编程

一、多线程带来的问题

以线程对一个全局变量进行自减为例引入多线程带来的问题。该操作分为三步:

  1. 将变量从内存移动到CPU相关寄存器
  2. 对它进行--操作
  3. --后的值写回内存

但是当系统进行内核态->用户态转换时,可能会进行线程的切换。倘若一个进程的--操作在执行到第2步时被切换,让别的线程再次操作这个全局变量,此时该变量的值再次改变。那么下一次线程切换回来时,寄存器中暂存的值被写回内存,从而导致数据不一致的问题。

相关概念

临界资源:多线程共享的全局变量等资源

临界区:每个线程内部,访问临界资源的代码,就叫做临界区

原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

为了保证多线程访问临界资源不会引发数据不一致等问题,线程库引入了互斥与同步的概念。

二、互斥

1、互斥与互斥量

互斥:在任何时刻,保证有且只有一个线程进入临界区,访问临界资源。

为了完成互斥操作,<pthread.h>库引入了互斥量的定义。

互斥量又被形象地称为==“锁”==,在进入临界区前加锁是每一个线程必须遵循的规定

  • 当一个线程竞争到互斥量时,该互斥量会被锁定,其余竞争互斥量的线程此时会因为竞争失败而陷入阻塞状态,暂时被挂起。
  • 当竞争到互斥量的线程将互斥量解锁后,其余线程才能申请到。
  • 倘若线程竞争到互斥量后因线程调度被切换走,那么该互斥量依然保持加锁状态,其余线程无法申请到互斥量,直到互斥量解锁。

若某线程在申请到互斥量后再次申请该互斥量,那么由于前一次申请将其加锁,第二次申请就会导致该线程被挂起,之后所有线程都无法申请到该互斥量,因此整个进程都会处于卡死状态(死锁)。

2、申请互斥量

I. 静态方法申请互斥量:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

通过一个宏来初始化互斥量。

注:通过宏初始化的互斥量不是同一个,因此在使用时,不会相互影响。

II. 动态方法申请互斥量:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)

  • mutex:要初始化的互斥量
  • attr:互斥量的设置参数,设置为NULL即可
  • 返回值:成功则返回0,失败则返回错误码。

注:互斥量为pthread_mutex_t类型的变量,需要用户定义,通过输出型参数的方式传给pthread_mutex_init()函数。

3、利用互斥量加锁与解锁

int pthread_mutex_lock(pthread_mutex_t *mutex) // 加锁

int pthread_mutex_unlock(pthread_mutex_t *mutex) // 解锁

成功则返回0,失败则返回错误码。

4、销毁互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex)

成功则返回0,失败则返回错误码。

注:动态方法申请的互斥量必须手动销毁。

5、互斥量综合应用——模拟抢票

// 模拟抢票
int tickets = 100;
pthread_mutex_t lock;

void* routine(void* arg)
{
    while (1)
    {
        // 进入临界区前先加锁
        pthread_mutex_lock(&lock);
        if (tickets > 0)
        {
            pthread_mutex_lock(&lock);
            usleep(20000); // 模拟购票成功后的系统响应时间
            cout << pthread_self() << " booking success! tickets left: " << tickets << endl;
            tickets--;
            pthread_mutex_unlock(&lock); // 锁不用了之后一定要解锁,因此每一个分支都要unlock
        }
        else 
        {
            pthread_mutex_unlock(&lock);
            break;
        }
    }
}

const int NUM = 5; // 线程数

int main()
{
    pthread_mutex_init(&lock, nullptr);
    pthread_t tids[NUM];
    for (int i = 0; i < NUM; ++i)
    {
        pthread_create(tids + i, nullptr, routine, nullptr);
        cout << tids[i] << "thread created successfully" << endl;
    }

    
    for (int i = 0; i < NUM; ++i)
    {
        pthread_join(tids[i], nullptr);
        cout << tids[i] << "thread quited successfully! tickets left = " << tickets << endl;
    }
    
    pthread_mutex_destroy(&lock);

    return 0;
}

6、互斥量的原子性

由于有多个线程需要竞争互斥量,因此互斥量本身也是一个临界资源,但是它具有原子性,其加解锁模型可被简化如下:

  1. 互斥量mutex存储在内存中,它的初始值为1。

  2. 当线程通过lock想申请互斥量时,首先要将自己标识是否持有锁的寄存器初始化为0,然后让该寄存器的内容与mutex进行交换。若交换后寄存器为1,则说明该线程申请到了互斥量,同时mutex由于交换变为0,说明线程将其锁住了。

  3. 如果此时该线程被切换,那么由于mutex已经变成0,此时其他线程就永远得不到1了,而原先的那个1还存储在被切换的线程的上下文数据中。

  4. 对于解锁操作,将mutex重新初始化为1,然后唤醒其它因申请互斥量被阻塞的线程即可。 对于汇编语言,将寄存器内容与内存地址上的内容交换是一条XCH指令就可以完成的,因此lock是具有原子性的。

7、线程饥饿问题

当多个竞争的线程被设置优先级之后,优先级越高,线程被给予的CPU时间越多。但是在某些极端情况下,比如存在多个高优先级线程,那么低优先级的线程可能永远无法被授予充足的CPU时间,从而导致这些线程处于==“饥饿”==状态。

三、同步

1、同步与条件变量

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源。这种顺序往往基于一种依赖关系,比如:A线程负责生产数据,B线程负责处理A生产的数据,那么在当前B没有数据可处理的情况下必须满足A在B之前执行。

为了完成同步操作,<pthread.h>库引入了条件变量的定义。

条件变量与互斥量搭配使用,使得线程以顺序等待而非竞争的方式访问临界资源。

2、条件变量的初始化

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr)

  • cond:要初始化的条件变量
  • attr:条件变量的设置参数,设置成NULL即可
  • 成功则返回0,失败则返回错误码。

3、线程等待满足条件变量

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex)

  • cond:等待条件变量cond被满足
  • mutex:互斥量
  • 成功则返回0,失败则返回错误码。

该函数的作用是:

  1. 将mutex释放。若不将mutex释放,而选择带着mutex一起被挂起,那么其它需要mutex进入临界区的线程都将陷入阻塞。

  2. 使本线程陷入阻塞,等待被唤醒。

  3. 被唤醒后,重新获取mutex。因为该线程依然处于临界区,所以被唤醒后需要重新获取锁以保证临界资源的安全。

注:Linux为每个条件变量维护了一个等待队列,当前被阻塞的线程被添加到该队列当中,等待被唤醒。

4、唤醒进程

解除一个线程由于等待条件变量满足而被阻塞的状态。若等待队列有多个线程,则具体解除哪一个由操作系统的调度算法决定。

I. 唤醒一个等待的线程

int pthread_cond_signal(pthread_cond_t *cond)

II. 唤醒所有等待的线程

int pthread_cond_broadcast(pthread_cond_t *cond)

5、条件变量的销毁

int pthread_cond_destroy(pthread_cond_t *cond)

6、条件变量的经典应用——生产者消费者模型

在生产者-消费者模型中,生产者Producer负责生产数据,而消费者Consumer负责获取并处理生产者生产的数据。多个生产者线程会在同一时间运行,生产数据;同时,也会有多个消费者线程同时运行,获取并处理数据。

产出的数据被存放在内存的某一个区域(即**“仓库”**,具体实现就是数组、队列…)。生产者只需要关心是否需要继续生产数据并将其存入仓库,而消费者也只需要关心仓库中是否有数据并将数据取走。

因此,该“仓库”的作用是:将生产者与消费者进行解耦,使其只需要关心自己的行为,相互之间没有影响,那么维护的成本也就更低了。

image-20220808172629047

I. 生产者与生产者、消费者与消费者的关系

他们都是互斥关系。

生产者之间竞争生产数据的资格,消费者之间竞争内存中的数据资源。本质上,都是竞争进入临界区的锁

II. 生产者与消费者的关系

他们是互斥与同步的关系。

互斥体现在:生产数据和使用数据这两种行为不能同时发生,即临界区一次只能由一个生产者线程或消费者线程进入。

同步体现在:当==“仓库”为空==,消费者需要等待生产者产出数据后才能消费。当==“仓库”为满==,生产者需要等待消费者取走数据后才能生产。即:在特定情况,它们需要被阻塞以等待对方的唤醒。

III. 代码实现

// 模板参数T表示生产者生产的数据类型
template<class T>
class ProducerConsumer
{
private:
    std::queue<T> bq;           // 仓库(这里用队列实现)
    int capacity;               // 仓库的容量(即队列的最大长度)
    pthread_mutex_t lock;       // 读写仓库的互斥量
    pthread_cond_t can_produce; // 生产者可以生产的条件变量(仓库为空)
    pthread_cond_t can_consume; // 消费者可以消费的条件变量(仓库不为空)
private:
    bool isEmpty()
    {
        return bq.empty();
    }
    
    bool isFull()
    {
        return bq.size() == capacity;
    }
public:
    ProducerConsumer(int cap) : capacity(cap) 
    {
        // 初始化互斥量与条件变量
        pthread_mutex_init(&lock, nullptr);
        pthread_cond_init(&can_produce, nullptr);
        pthread_cond_init(&can_consume, nullptr);
    }

    void produce(const T& data)
    {
        // 进入临界区前先上锁
        pthread_mutex_lock(&lock);
        while (isFull()) // while循环,避免伪唤醒,即:本线程醒来后被切换出去,其它线程取走数据,本线程回来的时候又没数据了
        {
            pthread_cond_wait(&can_produce, &lock); // 阻塞,等待被唤醒
        }
        
        // 走到这里说明有空间可以存放生产的数据
        bq.push(data);
        pthread_mutex_unlock(&lock); // 出临界区,解锁
        pthread_cond_signal(&can_consume); // 通知消费者可以消费

    }

    void consume(T* out)
    {
        // 进入临界区前先上锁
        pthread_mutex_lock(&lock);
        while (isEmpty())
        {
            pthread_cond_wait(&can_consume, &lock); // 等待消费
        }

        // 走到这里说明有数据供消费
        T data = bq.front();
        bq.pop();
        *out = data;

        pthread_mutex_unlock(&lock); // 出临界区,解锁
        pthread_cond_signal(&can_produce); // 通知生产者继续生产
    }

    ~ProducerConsumer()
    {
        // 销毁互斥量与条件变量
        pthread_mutex_destroy(&lock);
        pthread_cond_destroy(&can_produce);
        pthread_cond_destroy(&can_consume);
    }
};

四、线程池

线程池是一种线程的使用模式,其内部维护着多个线程,等待分配可并发执行的任务。

1、线程池的优势

  1. 避免了在处理短时间任务时创建与销毁线程的代价。

  2. 可以调节线程池中的线程数量,防止因为消耗过多内存而导致系统崩溃。

注:可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

2、线程池的应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短的程序。 比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大。

  2. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求在没有线程池的情况下,将使服务器短时间内产生大量线程,使内存到达极限,出现错误。

五、可重入与线程安全

线程安全:多个线程并发执行同一段代码时,产生的结果与预期相同。

重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

1、线程不安全的函数

  • 不保护共享变量(临界资源)的函数。

  • 函数状态随着被调用,状态发生变化,比如其内部有static变量。

  • 返回指向静态变量的指针的函数:因为这个静态变量可以被所有调用该函数的线程得到,从而在函数外也变成临界资源。

  • 调用了线程不安全函数的函数。

2、不可重入的函数

  • 调用了malloc/free函数:因为malloc函数是用全局链表来管理堆上的内存的。
  • 调用了标准I/O库函数:因为标准I/O库的很多实现都使用了全局的数据结构。
  • 使用了静态的数据结构

如果某个函数的参数全部是值传递,且函数内只是用栈上的局部变量,那么该函数就可以被断言为可重入函数

3、可重入与线程安全的区别

  1. 线程安全不一定是可重入的,但可重入函数则一定是线程安全的
  2. 线程安全依赖于互斥量和条件变量,而可重入函数不需要任何同步与互斥的操作,因此更加高效
  3. 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的。

4、STL与线程安全

STL不是线程安全的,因为STL的设计初衷是将性能挖掘到极致,一旦涉及到加锁保证线程安全,会对性能造成巨大的影响。此外,对于不同的容器,加锁方式的不同,性能可能也不同。

结论:如果需要在多线程环境下使用STL,需要用户自行保证线程安全!

六、死锁

死锁:是指两个及以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。

1、死锁四个必要条件

  1. 互斥:涉及的资源是非共享的,即资源被一个线程占用时,其他线程无法使用。

  2. 不可剥夺:线程已获得的资源,在末使用完之前,不得被其它线程强行剥夺

  3. 请求与保持:线程请求新资源的同时对已获得的资源保持不放

  4. 循环等待:若干线程之间形成一种头尾相接的循环等待资源的关系。

2、如何避免死锁

  • 破坏死锁的四个必要条件中的一个或多个。

  • 线程使用互斥量进行加锁的顺序要一致 。

  • 避免使用锁但不释放。

七、特殊类型的锁

1、自旋锁

对于普通的互斥锁,当互斥量被锁住时,其余申请锁的线程会被挂起,然而唤醒并切换一个被挂起的线程是有较大开销的。如果让没申请到锁的线程通过循环不断地尝试获取锁,使得该线程一直处于运行状态而非被挂起,那么就能节省线程切换的开销。这就是自旋锁的概念。

注意:如果临界区较大,即其它线程拿到锁进入临界区后需要运行较长时间才会释放锁,那么循环获取锁相较于线程切换反而开销更大。因此,自旋锁只适用于临界区较小的情况

2、读写锁(读者写者问题)

有些情况下,修改数据的次数很少,大多数都是读数据,而读往往伴随着耗时的查找,如果对读数据的代码段进行加锁,会使整个程序的效率大大降低。因此,读写锁应运而生,它使得一次只能有一个写线程,但是可以有多个读线程并发地读数据。

首先明确:

  • 读者与写者之间存在互斥关系,因为写会影响读的内容;
  • 写者与写者之间存在互斥关系,因为它们会互相影响
  • 读者与读者之间不存在任何关系,因为它们都不会修改数据

读写锁就是一把锁,但是它有两种状态。

  1. 当锁被读线程占用时,则所有读线程都可以共享这一把锁,即它们都可以进行读数据的操作,无需等待解锁而挂起。若写线程此时申请锁,则必须等当前所有读线程释放锁以后,才可以进行写操作。因此,可以理解成:读写锁维护了一个读线程计数器,记录当前有多少读线程正在使用这把锁,当计数器归零时,写线程才有权使用读写锁。

  2. 当锁被写线程占用时,则其余写线程和读线程都将被阻塞。

读写锁接口

初始化

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

销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)

加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)

3、乐观锁与悲观锁

I. 悲观锁

悲观锁在操作数据时比较"悲观",每次去拿数据的时候都认为别的线程也会同时修改数据,所以每次在拿数据的时候都会上锁,这样别的线程想拿到这个数据就必须阻塞等待。

II. 乐观锁

乐观锁是对于数据冲突保持一种"乐观态度",操作数据时不会对操作的数据进行加锁,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程并发编程 的相关文章

  • CAN通讯的byte序和bit序

    听别人说起CAN通讯协议的时候总说到Intel格式和motorola格式的时候 Intel格式如何 xff0c Motorola格式又如何 xff1f 觉得很有必要搞懂这些知识 xff0c 也看了相关资料 xff0c 可直到今天还没明白 真
  • ROS学习笔记(一):创建工作空间和功能包

    所有的ROS程序 xff0c 包括我们自己开发的程序 xff0c 都被组织成功能包 xff0c 而ROS的功能包被存放在称之为工作空间的目录下 因此 xff0c 在我们写程序之前 xff0c 第一步是创建一个工作空间以容纳我们的功能包 其实
  • PHP活动报名小程序系统源码 带后台管理程序

    活动报名小程序源码 xff0c 基于thinkphp开发的报名小程序源码 xff0c 带有后台管理 xff0c 用户发布活动信息 报名可以后台管理 xff0c 基本都还是可以的 不过需要注意的是 xff0c 用户注册部分是发送手机短信 xf
  • NVIDIA Jetson Xavier NX部署VINS-fusion-GPU

    组内大佬师兄今天抽出时间总结了一篇博客 xff0c 主要内容是 xff1a 把在阿木P450无人机上 xff0c 对自带的NVIDIA Jetson Xavier NX边缘计算机部署VINS fusion GPU教程 xff0c 并进行实验
  • 接口策略路由配置(通过流策略技术实现)

    策略路由 xff1a xff08 先于路由表 xff0c 且不会生成路由表 xff09 策略路由和路由策略都可以影响数据包的转发过程 xff0c 但他们对数据包的影响方式是不同的 本地策略路由 xff1a 仅对本机 下发的报文进行处理 xf
  • Mac Xcode崩溃 (打开ios项目引起崩溃)

    bug xff1a 每次打开此工程都会导致Xcode崩溃 其他工程没有问题 解决办法 xff1a 1 确定本地跟服务器没有需要更新和提交的代码 2 把本地工程移到废纸篓 3 从新check out工程 4 新工程完美运行 xff08 这样没
  • Mac上VScode使用clang-format格式化c++代码

    一 安装 需要安装插件c c 43 43 xff0c 不推荐使用clang format这个插件 xff0c 毕竟c c 43 43 里面已经支持了clang format格式化操作 安装这个clang format插件也有一个好处 xff
  • C#解决串口通信中接收数据时延迟处理与缓存处理的方法

    C 解决串口通信中接收数据时延迟处理与缓存处理的方法 时间 2011 1 21 14 04 29 来源 www cnblogs com 作者 杨少宁 利用串口进行通信 xff0c 当发送方 xff08 A xff09 将数据写入串口后 xf
  • 在gazebo中对机器人进行控制,并在rviz中同步显示

    在上一篇博客中 xff0c 我一步一步地建立了在gazebo仿真中能用的xacro文件 但是仿真时的模型是自由摆动的 xff0c 文末的时候我想对他进行控制 xff0c 但是篇幅太长 xff0c 所以新开一篇 参考ros control的内
  • 基于esp32-cam的监控小车

    1 购买器材 xff1a Eap32 cam L289N直流步进电机驱动 自锁开关 两节18650电池 5v电源 杜邦线若干 2 程序代码 2 1 下载代码 下载库函数 百度网盘 xff1a 链接 xff1a https pan baidu
  • APP Inventor -环境安装

    APP inventor可以实现直接在网页上操作 1 登录网址 xff1a http ai2 17coding net xff08 我使用的是微软浏览器 xff0c 用谷歌打开时显示无法登录 xff09 选择一键试用 2 下载Ai伴侣 xf
  • App inventor—安安机器人

    1 准备素材 2 程序设计 3 程序编程 3 1 3 2 3 3 总结 xff1a 1 换背景图片的时候 xff0c 设置图片 gt 用文本来存放图片的名称 xff08 特别注意后面的png xff0c 是图片的格式 xff09 2 手机震
  • 按键控制LED灯亮灭——基于arduino单片机

    程序设计 xff1a 函数 xff1a digitalRead 作用 xff1a 用来读取数字串口状态 xff0c HIGH还是LOW xff08 其实还有一种表达方式就是HIGH是 1 LOW是 0 xff0c 只是HIGH LOW更直观
  • JDY-31 蓝牙模块使用(HC-06)

    波特率要相同才能就行通讯 下载串口调试工具 打开串口调试工具 xff0c 配置参数 蓝牙连接 VCC 3 3V GND GND TXD RXD RXD TXD 配置蓝牙参数 发送指令 xff0c 要在指令后面添加 r n xff0c 或者让
  • arduino驱动LD3320语音识别模块

    LD3320 xff1a LD3320 是一颗基于非特定人语音识别 xff08 SI ASR xff1a Speaker Independent Automatic SpeechRecognition xff09 技术的语音识别 声控芯片
  • 无人机学习笔记之电池篇

    电池参数 电池的多少mAh 表示电池容量 xff0c 如1000mah电池 xff0c 如果以1000ma放电 xff0c 可持续放电1小时 如果以500mA放电 xff0c 可以持续放电2小时电池后面多少C 代表电池放电能力 这是普通锂电
  • 指纹锁—AS608指纹模块

    目录 一 工程内容 二 AS608指纹模块使用 1 AS608 与 USB转TTL模块 的接线 2 上位机配置 3 AS608与STC12C60A通讯 三 驱动MG995 xff08 180度舵机 xff09 1 MG995舵机数据手册 2
  • 手把手使用Python语音识别,进行语音转文字

    目录 0 太长不看系列 xff0c 直接使用 识别结果 1 Python调用标贝科技语音识别接口 xff0c 实现语音转文字 1 1 环境准备 xff1a 1 2 获取权限 1 2 1 登录 1 2 2 创建新应用 1 2 3 选择服务 1
  • python编译问题—Traceback (most recent call last): File “E:\python\pythonProject\文件读取.py“, line 3, in <

    Traceback most recent call last File 34 E python pythonProject 文件读取 py 34 line 3 in lt module gt f 61 open r 39 D 测试 tex
  • Python爬虫——爬取搜狗页面

    直接上代码 xff01 xff01 xff01 usr bin env python coding utf 8 8 需求 xff1a 爬取搜狗首页的页面数据 import requests if name 61 61 34 main 34

随机推荐

  • 数据结构-删除排序数组中的重复项

    class Solution public int removeDuplicates vector lt int gt amp nums int n lenght lenght 61 nums size 获取数组长度 if lenght 6
  • linux学习-虚拟机下载

    虚拟机下载 xff1a https www vmware com cn products workstation pro html 安装完成后 检测网络适配卡 xff1a 选择系统下载 OK
  • 硬件MSB最高位优先、LSB最低位优先的CRC计算原理详细解释和程序,正算反算成功等效,DS18B20和HTU31D传感器CRC

    wxleasyland 64 139 com 2022 7 以前写过 我学习CRC32 CRC16 CRC原理和算法的总结 xff08 与WINRAR结果一致 xff09 长篇 经过十几年又忘记了 这次碰到DS18B20进行CRC校验 xf
  • android4.4上sd卡的读写权限

    Google去年11月正式发布了Android 4 4 xff0c 代号为KitKat xff08 奇巧 xff0c 雀巢的一款巧克力品牌 xff09 xff0c 该系统带来了诸多新的特性 但需要注意的是 xff0c 该系统可能会让你之前一
  • android时间控件

    原文地址 xff1a http www 360doc com content 14 0617 16 18203124 387517665 shtml 前言 这一篇博客分别讲解Android平台下 xff0c 关于日期和时间的几个相关控件 包
  • Android开发-API指南-<application>[原创译文]

    转载地址 xff1a http blog sina com cn s blog 48d491300100zmga html lt application gt 英文原文 xff1a http developer android com gu
  • 新入职项目经理务必避免的七个常见错误

    在国内 xff0c 好多项目经理都是技术专家出身 xff0c 所以标题给了 34 新入职 34 由于旧习惯 xff0c 身上可能存在众多需要改进的地方 一 不明确自身的职责 你不在是个码农 xff1f 公司聘请你不是为了让你去写代码 xff
  • How Android Handles Touches

    Touch System overview Touch Event Framework Custom Touch Handling System Provided Touch Handlers System Provided Gesture
  • MFCC特征介绍

    MFCC特征介绍 在语音识别技术中 xff0c 需要提取音频的特征 xff0c 然后就可以使用该音频进行模型的训练或者是进行识别 xff0c 目前很常用的一种特征叫做MFCC特征 xff0c 又叫做梅尔倒谱系数特征 MFCC特征保留了语义相
  • 字符串大小比较问题

    首先 字符串比较函数 xff1a strcmp xff08 字符串1 字符串2 xff09 xff1b 其使用规则为 xff1a 若字符串1和字符串2相等 xff0c 返回0 若字符串1大于字符串2 xff0c 返回一个正整数 43 1 若
  • OBS 录制没有声音怎么办?

    1 检查obs设置 音频 是否是默认选项 2 检查win10 是否允许使用麦克风 1 xff09 右下角出现麦克风标识 2 xff09 设置 隐私 麦克风 xff0c 查看允许放开你的麦克风是否打开 如果上述还是为解决问题 xff0c 那么
  • android.os.deadObjectException异常

    deadObjectException异常 xff0c 说明应用的service已经停止 xff0c 要么是从操作系统中丧生 xff0c 要么从应用程序中终止
  • 2038问题

    2038年一月19号 xff0c 星期二 xff0c 凌晨3点14分7秒钟的时候 xff0c 如果Linux程序员会做恶梦的话 xff0c 那么梦的内容一定是关于这个日期的 xff0c 在这一秒钟滑过后 xff0c 凡是安装着linux的计
  • ChkBugReport工具for Android

    关于这个工具 xff0c 找到的资料都比较旧了 xff0c 貌似是索尼移动的开发人员开发的 xff0c 2014年左右的文章比较多 xff0c 应该是那个时候索尼移动还是比较鼎盛的时期吧 现在已经很少看到关于这个工具的文章了 xff0c G
  • kernel panic

    Linux kernel panic是很难定位和排查的重大故障 一旦系统发生了kernel panic xff0c 相关的日志信息非常少 xff0c 而一种常见的排查方法 重现法 又很难实现 xff0c 因此遇到kernel panic的问
  • PS域业务与CS域业务的区别

    1 CS和PS是针对核心网部分而言的 xff0c 两者的不同在于交换方式 CS是电路交换 xff0c 通信之前 xff0c 资源预留 xff0c 不同用户独占各自分配的资源 xff0c 没有统计复用 PS是包交换 xff0c 不同的用户可以
  • sh_脚本语法

    介绍 xff1a 1 开头 程序必须以下面的行开始 xff08 必须方在文件的第一行 xff09 xff1a bin sh 符号 用来告诉系统它后面的参数是用来执行该文件的程序 在这个例子中我们使用 bin sh来执行程序 当编写脚本完成时
  • 【深度学习系列(三)】:基于CNN+seq2seq公式识别系统实现 (1)

    这段时间一直在做公式识别相关的项目 xff0c 尝试了传统的方法 xff0c 效果不怎么好 想到能不能使用深度学习的方法进行相关方法 然后在github找到了相关代码 xff0c 这里做下分析 具体github地址 xff1a GitHub
  • 困惑多年,为什么printf可以重定向?

    很多人在用printf函数进行串口打印的时候 xff0c 都会被告知需要重定向fputc函数 xff08 别的平台可能不是这个函数 xff09 xff0c 让字符串数据输出到指定串口 xff0c 按照网上的教程也能很快解决 但是却没人告诉你
  • 多线程并发编程

    文章目录 多线程并发编程一 多线程带来的问题相关概念 二 互斥1 互斥与互斥量2 申请互斥量I 静态方法申请互斥量 xff1a II 动态方法申请互斥量 xff1a 3 利用互斥量加锁与解锁4 销毁互斥量5 互斥量综合应用 模拟抢票6 互斥