Linux多线程同步机制--条件变量

原创 Linux二进制 2024-07-05 08:20

一、引言

条件变量,作为多线程编程中的核心同步机制之一,其设计初衷在于解决线程间因等待某个条件成立而需暂停执行的问题。它允许线程在条件不满足时优雅地挂起,释放 CPU 资源,直到条件被其他线程修改为满足状态,从而被唤醒继续执行。条件变量的引入,不仅优化了程序的性能,还大大简化了线程间同步与通信的复杂度,是构建高效、稳定多线程应用的关键工具之一。

二、基本概念

条件变量是多线程编程中用于实现线程间通信和同步的重要工具。从本质上讲,它是一个线程等待的“标志”,当这个“标志”被设置为特定状态时,等待的线程将被唤醒并继续执行。具体来说,条件变量允许一个或多个线程暂停执行,等待某个特定条件的发生。这个条件通常与共享资源的状态或其他线程的操作结果相关。当条件未满足时,线程会被阻塞在条件变量上,释放 CPU 资源以供其他线程使用。一旦条件满足,其他线程可以通过特定的操作通知等待在条件变量上的线程,使其恢复执行。

三、工作原理

条件变量通常与互斥锁紧密结合使用,以确保对共享资源和条件的安全访问。当一个线程希望等待某个条件满足时,它首先需要获取与之关联的互斥锁,以保证在检查和操作条件时不会受到其他线程的干扰。然后,线程会检查条件是否已经满足。如果条件不满足,线程会调用条件变量的等待函数(如 pthread_cond_wait ),并在这个过程中自动释放之前获取的互斥锁,进入等待状态。当另一个线程完成了对共享资源的操作,使得条件满足时,它会获取相同的互斥锁,然后通过调用条件变量的通知函数(如 pthread_cond_signal 或 pthread_cond_broadcast)来唤醒等待在条件变量上的线程。被唤醒的线程不会立即开始执行,而是会重新竞争获取之前释放的互斥锁。只有成功获取到互斥锁的线程,才会再次检查条件是否确实满足,如果满足则继续执行后续的操作;如果条件仍然不满足,线程会再次进入等待状态。这种机制确保了线程之间在共享资源和条件状态上的安全协调,避免了竞争条件和不一致的情况发生。

问1】如果条件不满足,线程使用 pthread_cond_wait()挂起,并在这个过程中自动释放之前获取的互斥锁,进入等待状态。这里释放互斥锁是由 pthread_cond_wait() 函数内部释放的吗?

】是的,当线程调用 pthread_cond_wait() 或 pthread_cond_timedwait() 函数时,它们会在内部先释放互斥锁,然后将线程挂起等待条件变量。一旦其他线程调用 pthread_cond_signal() 或 pthread_cond_broadcast() 唤醒等待的线程,或者在 pthread_cond_timedwait() 的情况下超时发生,等待的线程会再次尝试获取互斥锁,然后重新检查之前挂起的条件。

以下是 pthread_cond_wait() 的典型使用模式:

  1. 线程首先锁定互斥锁。
  2. 检查条件是否满足。
  3. 如果条件不满足,线程调用 pthread_cond_wait() 并传入互斥锁的地址。
  4. pthread_cond_wait() 函数内部释放互斥锁,并将线程放入等待状态。
  5. 当条件变量被触发时,等待的线程被唤醒。
  6. 线程再次尝试锁定互斥锁(这可能涉及等待,如果其他线程当前持有锁)。
  7. 一旦互斥锁被重新获取,线程再次检查条件。

使用 pthread_cond_wait() 的示例代码:

pthread_mutex_lock(&mutex);
while (!condition) {
pthread_cond_wait(&cond, &mutex); // 释放互斥锁并等待条件变量
}
// 条件已满足,继续执行
pthread_mutex_unlock(&mutex);

在这个模式中,condition 是线程等待的条件,mutex 是与条件变量 cond 关联的互斥锁。当条件不满足时,pthread_cond_wait() 被调用,线程会释放互斥锁并等待。当条件被满足并由其他线程发出信号后,等待的线程会被唤醒,重新获取互斥锁,并再次检查条件。

这种机制确保了在等待期间不会浪费 CPU 资源去不断检查条件,同时也避免了在条件满足时多个线程同时访问共享资源造成的竞争条件。

问2】当条件被满足并由其他线程发出信号后,等待的线程会被唤醒,重新获取互斥锁,并再次检查条件。这里说的重新获取互斥锁是由谁去重新获得,是 pthread_cond_wait() 函数内部实现的吗?并再次检查条件,是谁去检查?

】是的,当条件被满足并由其他线程发出信号后,等待的线程会被唤醒的过程是由 pthread_cond_wait() 函数内部实现的。以下是详细步骤:

  1. 唤醒线程:当 pthread_cond_signal 或 pthread_cond_broadcast 被调用时,等待条件变量的线程会被唤醒。

  2. 重新获取互斥锁:唤醒的线程在 pthread_cond_wait() 返回之前会自动尝试重新获取之前已经释放的互斥锁。这是 pthread_cond_wait() 函数的一部分,它确保了在线程从等待状态返回并继续执行之前互斥锁是被锁定的。

  3. 再次检查条件:线程在重新获取互斥锁之后,需要再次检查之前挂起的条件是否仍然有效。这是因为在线程等待期间,其他线程可能已经改变了条件,或者可能有多个线程被唤醒但条件只对其中一个线程有效。

    以下是使用 pthread_cond_wait() 的典型代码示例,展示了如何等待条件变量,以及如何在被唤醒后重新检查条件:

    pthread_mutex_lock(&mutex);
    while (!condition) {
    // 等待条件变量,互斥锁在此过程中会被释放
    pthread_cond_wait(&cond, &mutex);
    // 这里互斥锁已经被pthread_cond_wait内部重新获取
    }
    // 条件已满足,继续执行
    // 此时互斥锁是锁定的,共享资源是安全的
    pthread_mutex_unlock(&mutex);

这个示例中,condition 是线程等待的条件,mutex 是与条件变量 cond 关联的互斥锁。当条件不满足时,线程调用 pthread_cond_wait() 并释放互斥锁,然后挂起等待。当条件被满足并由其他线程发出信号后,等待的线程会被唤醒,pthread_cond_wait() 函数会帮助线程重新获取互斥锁,然后线程再次检查条件。

这个机制确保了线程在等待和唤醒过程中对共享资源的访问是互斥的,从而避免了竞态条件。开发者需要在设计同步逻辑时考虑到这一点,确保条件变量的使用是安全的。

四、函数原型

条件变量是一种同步机制,用于在多线程编程中等待某个条件的发生。在 C 语言中,条件变量的 API 通常由 POSIX 线程库(pthreads)提供。以下是条件变量相关的主要 API 函数,包括它们的函数原型、参数和返回值:

  1. pthread_cond_init() - 初始化条件变量

  • cond:指向pthread_cond_t结构的指针,用于创建条件变量。
  • attr:指向pthread_condattr_t结构的指针,包含条件变量的属性。如果为NULL,则使用默认属性。
  • 函数原型:

    int pthread_cond_init(pthread_cond_t *restrict cond,
                          const pthread_condattr_t *restrict attr)
    ;
  • 参数:

  • 返回值:成功时返回0,出错时返回相应的错误码。

  • pthread_cond_destroy() - 销毁条件变量

    • cond:指向之前初始化的条件变量的指针。
    • 函数原型:

      int pthread_cond_destroy(pthread_cond_t *cond);
    • 参数:

    • 返回值:成功时返回0,出错时返回相应的错误码。

    • pthread_cond_wait() - 等待条件变量

      • cond:指向条件变量的指针。
      • mutex:指向已锁定的互斥锁的指针。此互斥锁在等待条件变量前必须被锁定,并且在等待期间将被释放。
      • 函数原型:

        int pthread_cond_wait(pthread_cond_t *restrict cond,
                              pthread_mutex_t *restrict mutex)
        ;
      • 参数:

      • 返回值:成功时返回0,出错或被唤醒时返回相应的错误码。

      • pthread_cond_timedwait() - 带超时的等待条件变量

        • cond:指向条件变量的指针。
        • mutex:指向已锁定的互斥锁的指针。
        • abstime:指向struct timespec的指针,表示超时时间。这是一个绝对时间,通常使用clock_gettime()函数获取当前时间并加上超时时长来设置。
        • 函数原型:

          int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                                     pthread_mutex_t *restrict mutex,
                                     const struct timespec *restrict abstime)
          ;
        • 参数:

        • 返回值:成功且未超时则返回0,出错时返回错误码,超时返回ETIMEDOUT

        • pthread_cond_signal() - 唤醒等待条件变量的一个线程

          • cond:指向条件变量的指针。
          • 函数原型:

            int pthread_cond_signal(pthread_cond_t *cond);
          • 参数:

          • 返回值:成功时返回0,出错时返回相应的错误码。

          • pthread_cond_broadcast() - 唤醒等待条件变量的所有线程

            • cond:指向条件变量的指针。
            • 函数原型:

              int pthread_cond_broadcast(pthread_cond_t *cond);
            • 参数:

            • 返回值:成功时返回0,出错时返回相应的错误码。

            • pthread_condattr_init() - 初始化条件变量属性

              • attr:指向pthread_condattr_t结构的指针。
              • 函数原型:

                int pthread_condattr_init(pthread_condattr_t *attr);
              • 参数:

              • 返回值:成功时返回0,出错时返回相应的错误码。

              • pthread_condattr_destroy() - 销毁条件变量属性

                • attr:指向之前初始化的条件变量属性的指针。
                • 函数原型:

                  int pthread_condattr_destroy(pthread_condattr_t *attr);
                • 参数:

                • 返回值:成功时返回0,出错时返回相应的错误码。

                  这些函数构成了 POSIX 线程库中条件变量的完整 API,它们允许开发者在多线程程序中实现复杂的同步逻辑。正确使用这些 API 对于避免竞态条件、死锁和其他同步问题至关重要。

                  五、特点与挑战

                  条件变量具有以下优点:

                  1. 高效协作:通过条件变量,线程可以在条件不满足时进行等待,避免了无效的忙碌循环,提高了 CPU 资源的利用率。
                  2. 灵活通信:允许线程根据复杂的条件进行等待和唤醒,增强了线程间通信的灵活性和准确性。
                  3. 减少竞争:可以有效地协调线程对共享资源的访问,减少了线程之间的竞争和冲突。

                  然而,使用条件变量也存在一些挑战:

                  1. 编程复杂性:使用条件变量需要仔细处理互斥锁和条件变量的交互,不当的使用可能导致死锁、竞态条件等难以调试的错误。
                  2. 虚假唤醒风险:虽然不常见,但存在线程被虚假唤醒的可能性,即线程在条件未满足时被唤醒。因此,在使用条件变量时,通常需要在等待条件的循环中再次检查条件。
                  3. 理解难度高:对于初学者来说,理解条件变量的工作原理和正确使用方式可能具有一定的难度,需要对线程同步的概念有深入的理解。

                  六、C 语言实现案例

                  以下是一个使用条件变量的生产者-消费者模型实现:

                  #include 
                  #include 
                  #include 
                  #include 

                  // 定义缓冲区大小
                  #define BUFFER_SIZE 10

                  // 定义缓冲区结构,包含数据缓冲、索引、互斥锁和条件变量
                  typedef struct {
                      int buffer[BUFFER_SIZE]; // 数据缓冲区
                      int in, out;             // 索引,in指向下一个写入位置,out指向下一个读取位置
                      pthread_mutex_t mutex;   // 互斥锁,用于同步对缓冲区的访问
                      pthread_cond_t notFull; // 条件变量,生产者在缓冲区未满时等待
                      pthread_cond_t notEmpty; // 条件变量,消费者在缓冲区非空时等待
                  } Buffer;

                  // 初始化缓冲区
                  void buffer_init(Buffer* buf) {
                      buf->in = buf->out = 0// 初始化索引
                      pthread_mutex_init(&buf->mutex, NULL); // 初始化互斥锁
                      pthread_cond_init(&buf->notFull, NULL); // 初始化notFull条件变量
                      pthread_cond_init(&buf->notEmpty, NULL); // 初始化notEmpty条件变量
                  }

                  // 生产者线程函数
                  voidproducer(void* arg) {
                      Buffer* buf = (Buffer*)arg; // 从传入的参数中获取Buffer结构的指针。
                      int value = 1// 初始化生产的数据值。

                      while (value <= BUFFER_SIZE) { // 当生产的数据值小于或等于BUFFER_SIZE时循环。
                          pthread_mutex_lock(&buf->mutex); // 锁定互斥锁,进入临界区。

                          // 检查缓冲区是否已满。如果满了,生产者将等待。
                          while ((buf->in + 1) % BUFFER_SIZE == buf->out) {
                              pthread_cond_wait(&buf->notFull, &buf->mutex);
                              // 如果缓冲区满,生产者在notFull条件变量上等待,同时保持互斥锁。
                          }

                          // 缓冲区未满,生产者可以放入数据。
                          buf->buffer[buf->in] = value; // 将数据放入缓冲区。
                          buf->in = (buf->in + 1) % BUFFER_SIZE; // 更新生产索引,如果达到末尾则回到开始位置。

                          printf("Produced value: %d\n", value); // 打印生产的数据值。

                          // 通知消费者,缓冲区中有新数据可以消费。
                          pthread_cond_signal(&buf->notEmpty);

                          pthread_mutex_unlock(&buf->mutex); // 释放互斥锁,退出临界区。

                          value++; // 准备生产下一项数据。
                          usleep(500000); // 线程休眠一段时间,模拟生产过程所需时间。
                      }
                      return NULL// 线程结束。
                  }

                  // 消费者线程函数
                  voidconsumer(void* arg) {
                      Buffer* buf = (Buffer*)arg; // 从传入的参数中获取Buffer结构的指针。
                      int value; // 用于存储从缓冲区取出的数据。

                      while (1) { // 无限循环,直到消费者决定退出。
                          pthread_mutex_lock(&buf->mutex); // 锁定互斥锁,进入临界区。

                          // 检查缓冲区是否为空。如果为空,消费者将等待。
                          while (buf->in == buf->out) {
                              pthread_cond_wait(&buf->notEmpty, &buf->mutex);
                              // 如果缓冲区空,消费者在notEmpty条件变量上等待,同时保持互斥锁。
                          }

                          // 缓冲区不为空,消费者可以取出数据。
                          value = buf->buffer[buf->out]; // 从缓冲区取出数据。
                          buf->out = (buf->out + 1) % BUFFER_SIZE; // 更新消费索引,如果达到末尾则回到开始位置。

                          printf("Consumed value: %d\n", value); // 打印消费的数据值。

                          // 通知生产者,缓冲区有空间可以生产更多数据。
                          pthread_cond_signal(&buf->notFull);

                          pthread_mutex_unlock(&buf->mutex); // 释放互斥锁,退出临界区。

                          if (value >= BUFFER_SIZE) break// 如果取出的数据值达到或超过BUFFER_SIZE,退出循环。
                          usleep(500000); // 线程休眠一段时间,模拟消费过程所需时间。
                      }
                      return NULL// 线程结束。
                  }

                  int main() {
                      pthread_t prod, cons; // 线程ID
                      Buffer buf; // 创建缓冲区实例

                      // 初始化缓冲区
                      buffer_init(&buf);

                      // 创建生产者线程
                      if (pthread_create(&prod, NULL, producer, &buf) != 0) {
                          perror("Failed to create producer thread");
                          exit(EXIT_FAILURE);
                      }

                      // 创建消费者线程
                      if (pthread_create(&cons, NULL, consumer, &buf) != 0) {
                          perror("Failed to create consumer thread");
                          exit(EXIT_FAILURE);
                      }

                      // 等待生产者线程结束
                      pthread_join(prod, NULL);
                      // 等待消费者线程结束
                      pthread_join(cons, NULL);

                      // 清理互斥锁和条件变量
                      pthread_mutex_destroy(&buf.mutex);
                      pthread_cond_destroy(&buf.notFull);
                      pthread_cond_destroy(&buf.notEmpty);

                      printf("Production and consumption complete.\n");

                      return 0;
                  }

                  这个示例中的关键点详细阐述如下:

                  1. 缓冲区大小定义 (BUFFER_SIZE)

                  • BUFFER_SIZE 是一个宏,定义了环形缓冲区的大小。这个值决定了缓冲区可以存储多少个数据项。在生产者-消费者模型中,缓冲区的大小直接影响到生产者和消费者线程的同步行为。
                • 缓冲区结构 (Buffer)

                  • Buffer 结构体包含了缓冲区所需的所有元素:一个整型数组用于存储数据 (buffer[]),两个整型变量 in 和 out 用作索引,分别指向下一个生产和消费的位置。此外,包含一个互斥锁 (mutex) 用于同步对缓冲区的访问,以及两个条件变量 (notFull 和 notEmpty),分别用于同步生产者和消费者的行为。
                • 生产者函数 (producer)

                  • producer 函数模拟生产者的行为。它生成一系列数据,并尝试将这些数据放入缓冲区。如果缓冲区已满,生产者将等待 notFull 条件变量,直到缓冲区有空间可用。生产者使用互斥锁来确保在放入数据时缓冲区不会被其他线程访问。
                • 消费者函数 (consumer)

                  • consumer 函数模拟消费者的行为。它从缓冲区取出数据并处理。如果缓冲区为空,消费者将等待 notEmpty 条件变量,直到缓冲区中有数据可取。消费者同样使用互斥锁来确保在取出数据时缓冲区的安全性。
                • 时间模拟 (usleep)

                  • usleep 函数用于使线程休眠一段指定的时间(以微秒为单位)。在这个示例中,usleep 模拟了生产和消费操作所需的时间延迟,这有助于观察和理解线程间的同步行为。
                • 主函数中的初始化和线程创建

                  • 在 main 函数中,首先初始化 Buffer 结构体,包括互斥锁和条件变量。然后创建生产者和消费者线程,分别执行 producer 和 consumer 函数。
                • 等待线程完成 (pthread_join)

                  • 使用 pthread_join 等待生产者和消费者线程完成它们的任务。这个函数调用会阻塞,直到指定的线程结束。这是确保程序在所有线程完成之前不会退出的关键。
                • 清理资源

                  • 在所有线程完成后,使用 pthread_mutex_destroy 和 pthread_cond_destroy 清理互斥锁和条件变量,释放它们占用的资源。
                • 同步机制的展示

                  • 这个模型展示了如何使用条件变量和互斥锁来同步对共享资源(缓冲区)的访问。生产者和消费者根据缓冲区的状态(满或空)来决定是继续操作还是等待,并在条件满足时被唤醒。

                  通过这个示例,我们可以看到条件变量在多线程同步中的强大作用,它们提供了一种有效的方式来协调线程间的协作,确保共享资源的正确和安全访问。

                  编译并执行程序,结果如下:

                  [root@localhost cond]# gcc pthread_cond_test.c -o pthread_cond_test -lpthread
                  [root@localhost cond]# ls
                  pthread_cond_test pthread_cond_test.c
                  [root@localhost cond]# ./pthread_cond_test
                  Produced value: 1
                  Consumed value: 1
                  Produced value: 2
                  Consumed value: 2
                  Produced value: 3
                  Consumed value: 3
                  Produced value: 4
                  Consumed value: 4
                  Produced value: 5
                  Consumed value: 5
                  Produced value: 6
                  Consumed value: 6
                  Produced value: 7
                  Consumed value: 7
                  Produced value: 8
                  Consumed value: 8
                  Produced value: 9
                  Consumed value: 9
                  Produced value: 10
                  Consumed value: 10
                  Production and consumption complete.

                  这个输出结果验证了生产者-消费者模型的正确实现,其中条件变量和互斥锁被用来确保数据项能够安全地在生产者和消费者之间传递。

                  七、总结

                  条件变量在多线程编程中是实现复杂同步逻辑的重要工具,但它的正确运用并非易事。开发者需要深入理解其工作原理和机制,谨慎处理各种细节和潜在的问题。只有这样,才能充分发挥条件变量的优势,构建出高效、稳定且可靠的多线程应用程序。同时,不断的实践和经验积累也是掌握条件变量的关键,通过实际项目中的应用和调试,开发者能够更加熟练地运用这一强大的同步机制,提升多线程编程的能力和水平。


                  Linux二进制 Linux编程、内核模块、网络原创文章分享,欢迎关注"Linux二进制"微信公众号
                  评论
                  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
                    丙丁先生 2025-01-07 09:25 80浏览
                  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
                    优思学院 2025-01-06 12:03 114浏览
                  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
                    华普微HOPERF 2025-01-06 15:29 125浏览
                  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
                    华普微HOPERF 2025-01-06 17:23 141浏览
                  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
                    知白 2025-01-07 15:02 71浏览
                  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
                    hai.qin_651820742 2025-01-07 14:52 42浏览
                  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
                    GIRtina 2025-01-06 11:10 104浏览
                  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
                    GIRtina 2025-01-07 11:02 66浏览
                  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
                    Industio_触觉智能 2025-01-06 10:43 87浏览
                  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
                    知白 2025-01-06 12:04 170浏览
                  我要评论
                  0
                  点击右上角,分享到朋友圈 我知道啦
                  请使用浏览器分享功能 我知道啦