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

Linux/AndroidOS中进程间的通信线程间的同步 - 信号量

1 概述

本文将介绍 POSIX 信号量,它允许进程和线程同步对共享资源的访问。有两种类型的 POSIX 信号量:

  • 命名信号量:这种信号量拥有一个名字。通过使用相同的名字调用 sem_open(),不相关的进程能够访问同一个信号量。
  • 未命名信号量:这种信号量没有名字,相反,它位于内存中一个预先商定的位置处。未命名信号量可以在进程之间或一组线程之间共享。当在进程之间共享时,信号量必须位于一个共享内存区域中。当在线程之间共享时,信号量可以位于被这些线程共享的一块内存区域中(如在堆上或在一个全局变量中)。

POSIX 信号量是一个整数,其值是不能小于 0 的。如果一个进程试图将一个信号量的值减小到小于 0,那么取决于所使用的函数,调用会阻塞或返回一个表明当前无法执行相应操作的错误。
一些系统并没有完整地实现 POSIX 信号量,一个典型的约束是只支持未命名线程共享的信号量。只有在 Linux 2.6 以及带 NPTL 的 glibc 上,完整的POSIX 信号量实现才可用。

2 命名信号量

要使用命名信号量必须要使用下列函数。

  • sem_open()函数打开或创建一个信号量并返回一个句柄以供后续调用使用,如果这个调用会创建信号量的话还会对所创建的信号量进行初始化。
  • sem_post(sem)和 sem_wait(sem)函数分别递增和递减一个信号量值。
  • sem_getvalue()函数获取一个信号量的当前值。
  • sem_close()函数删除调用进程与它之前打开的一个信号量之间的关联关系。
  • sem_unlink()函数删除一个信号量名字并将其标记为在所有进程关闭该信号量时删除该信号量。

POSIX 并没有规定如何实现命名信号量。一些 UNIX 实现将它们创建成位于标准文件系统上一个特殊位置处的文件。在 Linux 上,命名信号量被创建成小型 POSIX 共享内存对象,其名字的形式为 sem.name,这些对象将被放在一个挂载在/dev/shm 目录之下的专用 tmpfs 文件系统中。这个文件系统具备内核持久性——它所包含的信号量对象将会持久,即使当前没有进程打开它们,但如果系统被关闭的话,这些对象就会丢失。

在 Linux 上从内核 2.6 起开始支持命名信号量。

2.1 打开一个命名信号量

sem_open()函数创建和打开一个新的命名信号量或打开一个既有信号量。

#include <fcntl.h>			/* Defines 0*constants */
#include<sys/stat.h>		/* Defines mode constants */
#include <semaphore.h>/*Returns pointer to semaphore on success, Or SEM_FAILED on error*/
sem_t *sem_open(const char *name, int oflag, .../*mode t mode, unsigned int value */);

name 参数标识出了信号量。

oflag 参数是一个位掩码,它确定了是打开一个既有信号量 / 创建并打开一个新信号量。如果 oflag 为 0,那么将访问一个既有信号量。如果在 oflag 中指定了 O_CREAT,并且与给定的 name对应的信号量的不存在,那么就创建一个新信号量。如果在 oflag 中同时指定了 O_CREAT 和O_EXCL,并且与给定的 name 对应的信号量已经存在,那么 sem_open()就会失败。

如果 sem_open()被用来打开一个既有信号量,那么调用就只需要两个参数如果在 flags中指定了 O_CREAT,那么就还需要另外两个参数:mode 和 value。(如果与 name 对应的信号量已经存在,那么这两个参数会被忽略。)具体如下。

  • mode 参数是一个位掩码,它指定了施加于新信号量之上的权限。这个参数能取的位值与文件上的位值是一样的并且与 open()一样,mode 参数中的值会根据进程的 umask 来取掩码。Linux中,在打开一个信号量时会将访问模式默认成 O_RDWR,因为大多数使用信号量的应用程序都同时会用到sem_post()和 sem_wait(),从而需要读取和修改一个信号量的值。这意味着需要确保将读权限和写权限赋给每一类需要访问这个信号量的用户——owner、group 以及 other。
  • value 参数是一个无符号整数,它指定了新信号量的初始值。信号量的创建和初始化操作是原子的。

不管是创建一个新信号量还是打开一个既有信号量,sem_open()都会返回一个指向一个sem_t 值的指针,而在后续的调用中则可以通过这个指针来操作这个信号量。sem_open()在发生错误时会返回 SEM_FAILED 值。(在Linux上,SEM_FAILED 被定义成了((sem_t *) 0) )。

POSIX 声称当在 sem_open()的返回值指向的 sem_t 变量的副本上执行操作(sem_post()、sem_wait()等)时结果是未定义的。换句话说,像下面这种使用 sem2 的做法是不允许的

sem_t *sp,sem2
sp = sem_open(...);
sem2 =*sp;
sem wait(&sem2);

通过 fork()创建的子进程会继承其父进程打开的所有命名信号量的引用。在 fork()之后,父进程和子进程就能够使用这些信号量来同步它们的动作了。

示例程序
程序为 sem_open()函数提供了一个命令行界面。在 usageError()函数中给出了这个程序的命令格式。
下面的 shell 会话日志演示了如何使用这个程序。首先使用 umask 命令来否决 other 用户的所有权限,然后互斥地创建一个信号量并查看包含该命名信号量的 Linux 特有的虚拟目录中的内容。

$ umask 007
$ ./psem_create -cx /demo 666
$ ls -l /dev/shm/sem.demo
-rw-rw---- 1 dockdroid dockdroid 32 May  6 17:23 /dev/shm/sem.demo

ls 命令的输出表明进程的 umask 覆盖了为 other 用户指定的 read+write 权限。
如果再次使用同样的名字来互斥地创建一个信号量,那么这个操作就会失败,因为这个名字已经存在了。

$ ./psem_create -cx /demo 666
ERROR [EEXIST File exists] sem_open
/*
psem/psem_create.cCreate a POSIX named semaphore.Usage as shown in usageError().On Linux, named semaphores are supported with kernel 2.6 or later, anda glibc that provides the NPTL threading implementation.
*/#include <semaphore.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"static void
usageError(const char *progName)
{fprintf(stderr, "Usage: %s [-cx] name [octal-perms [value]]\n", progName);fprintf(stderr, "    -c   Create semaphore (O_CREAT)\n");fprintf(stderr, "    -x   Create exclusively (O_EXCL)\n");exit(EXIT_FAILURE);
}int
main(int argc, char *argv[])
{int flags, opt;mode_t perms;unsigned int value;sem_t *sem;flags = 0;while ((opt = getopt(argc, argv, "cx")) != -1) {switch (opt) {case 'c':   flags |= O_CREAT;           break;case 'x':   flags |= O_EXCL;            break;default:    usageError(argv[0]);}}if (optind >= argc)usageError(argv[0]);/* Default permissions are rw-------; default semaphore initializationvalue is 0 */perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :getInt(argv[optind + 1], GN_BASE_8, "octal-perms");value = (argc <= optind + 2) ? 0 : getInt(argv[optind + 2], 0, "value");sem = sem_open(argv[optind], flags, perms, value);if (sem == SEM_FAILED)errExit("sem_open");exit(EXIT_SUCCESS);
}

2.2 关闭一个信号量

当一个进程打开一个命名信号量时,系统会记录进程与信号量之间的关联关系。sem_close()函数会终止这种关联关系(即关闭信号量),释放系统为该进程关联到该信号量之上的所有资源,并递减引用该信号量的进程数。

#include <semaphore.h>
int sem_close(sem_t *sem);

打开的命名信号量在进程终止或进程执行了一个 exec()时会自动被关闭。
关闭一个信号量并不会删除这个信号量,而要删除信号量则需要使用 sem_unlink()。

2.3 删除一个命名信号量

sem_unlink()函数删除通过 name 标识的信号量并将信号量标记成一旦所有进程都使用完这个信号量时就销毁该信号量(这可能立即发生,前提是所有打开过该信号量的进程都已经关闭了这个信号量)。

#include <semaphore.h>
int sem_unlink(const char *name);
/* psem/psem_unlink.cUnlink a POSIX named semaphore.On Linux, named semaphores are supported with kernel 2.6 or later, anda glibc that provides the NPTL threading implementation.
*/
#include <semaphore.h>
#include "tlpi_hdr.h"int
main(int argc, char *argv[])
{if (argc != 2 || strcmp(argv[1], "--help") == 0)usageErr("%s sem-name\n", argv[0]);if (sem_unlink(argv[1]) == -1)errExit("sem_unlink");exit(EXIT_SUCCESS);
}

3 信号量操作

POSIX 信号量也是一个整数并且系统不会允许其值小于 0。POSIX 信号量的操作具体包括:

  • 修改信号量值的函数———sem_post()和 sem_wait(),一次只操作一个信号量。
  • sem_post()和 sem_wait()函数只对信号量值加 1 和减 1。

3.1 等待一个信号量

sem_wait()函数会递减(减小 1)sem 引用的信号量的值。

#include <semaphore.h>
int sem_wait(sem_t *sem);
  • 如果信号量的当前值大于 0,那么 sem_wait()会立即返回。
  • 如果信号量的当前值等于 0,那么 sem_wait()会阻塞直到信号量的值大于 0 为止,当信号量值大于 0 时该信号量值就被递减并且 sem_wait()会返回。
  • 如果一个阻塞的 sem_wait()调用被一个信号处理器中断了,那么它就会失败并返回 EINTR错误,不管在使用 sigaction()建立这个信号处理器时是否采用了 SA_RESTART 标记。(在其他一些 UNIX 实现上,SA_RESTART 会导致 sem_wait()自动重启。)

下面示例程序为 sem_wait()函数提供了一个命令行界面,稍后就会演示如何使用这个程序。

/* psem/psem_wait.cDecrease the value of a POSIX named semaphore.See also psem_post.c.On Linux, named semaphores are supported with kernel 2.6 or later, anda glibc that provides the NPTL threading implementation.使用 sem_wait()来递减一个 POSIX 信号量 
*/
#include <semaphore.h>
#include "tlpi_hdr.h"int
main(int argc, char *argv[])
{sem_t *sem;if (argc < 2 || strcmp(argv[1], "--help") == 0)usageErr("%s sem-name\n", argv[0]);sem = sem_open(argv[1], 0);if (sem == SEM_FAILED)errExit("sem_open");if (sem_wait(sem) == -1)errExit("sem_wait");printf("%ld sem_wait() succeeded\n", (long) getpid());exit(EXIT_SUCCESS);
}

sem_trywait()函数是 sem_wait()的一个非阻塞版本。

#include <semaphore.h>
int sem_trywait(sem_t *sem);

如果递减操作无法立即被执行,那么 sem_trywait()就会失败并返回 EAGAIN 错误。

sem_timedwait()函数是 sem_wait()的另一个变体,它允许调用者为调用被阻塞的时间量指定一个限制。

#define _XOPEN_SOURCE 600
#include <semaphore.h>
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

如果 sem_timedwait()调用因超时而无法递减信号量,那么这个调用就会失败并返回ETIMEDOUT 错误。

abs_timeout 参数是一个结构,它将超时时间表示成了自新纪元到现在为止的秒数和纳秒数的绝对值。如果需要指定一个相对超时时间,那么就必须要使用 clock_gettime()获取 CLOCK_REALTIME 时钟的当前值并在该值上加上所需的时间量来生成一个适合在sem_timedwait()中使用的 timespec 结构。

3.2 发布一个信号量

sem_post()函数递增(增加 1)sem 引用的信号量的值。

#include <semaphore.h>
int sem_post(sem_t *sem);

如果在 sem_post()调用之前信号量的值为 0,并且其他某个进程(或线程)正在因等待递减这个信号量而阻塞,那么该进程会被唤醒,它的 sem_wait()调用会继续往前执行来递减这个信号量。如果多个进程(或线程)在 sem_wait()中阻塞了,并且这些进程的调度采用的是默认的循环时间分享策略,那么哪个进程会被唤醒并允许递减这个信号量是不确定的。(POSIX 信号量仅仅是一种同步机制,而不是一种排队机制。)
递增一个 POSIX 信号量对应于释放一些共享资源以供其他进程或线程使用。

示例程序为 sem_post()函数提供了一个命令行界面。

/* psem/psem_post.cIncrease the value of a POSIX named semaphore.See also psem_wait.c.On Linux, named semaphores are supported with kernel 2.6 or later, anda glibc that provides the NPTL threading implementation.使用 sem_post()递增一个 POSIX 信号量
*/
#include <semaphore.h>
#include "tlpi_hdr.h"int
main(int argc, char *argv[])
{sem_t *sem;if (argc != 2)usageErr("%s sem-name\n", argv[0]);sem = sem_open(argv[1], 0);if (sem == SEM_FAILED)errExit("sem_open");if (sem_post(sem) == -1)errExit("sem_post");exit(EXIT_SUCCESS);
}

3.3 获取信号量的当前值

sem_getvalue()函数将 sem 引用的信号量的当前值通过 sval 指向的 int 变量返回。

#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);

如果一个或多个进程(或线程)当前正在阻塞以等待递减信号量值,那么 sval 中的返回值将取决于实现。POSIX 允许两种做法:0 或一个绝对值等于在 sem_wait()中阻塞的等待者数目的负数。Linux 和其他一些实现采用了第一种行为,而另一些实现则采用了后一种行为。

注意在 sem_getvalue()返回时,sval 中的返回值可能已经过时了。依赖于 sem_getvalue()返回的信息在执行后续操作时未发生变化的程序将会碰到检查时、使用时(time-of-check、time-of-use)的竞争条件。

示例程序使用了 sem_getvalue()来获取名字通过命令行参数指定的信号量的值,然后在标准输出上显示该值。

/* psem_getvalue.cObtain the value of a POSIX named semaphore.On Linux, named semaphores are supported with kernel 2.6 or later, anda glibc that provides the NPTL threading implementation.使用 sem_getvalue()获取一个 POSIX 信号量的值
*/
#include <semaphore.h>
#include "tlpi_hdr.h"int
main(int argc, char *argv[])
{int value;sem_t *sem;if (argc != 2)usageErr("%s sem-name\n", argv[0]);sem = sem_open(argv[1], 0);if (sem == SEM_FAILED)errExit("sem_open");if (sem_getvalue(sem, &value) == -1)errExit("sem_getvalue");printf("%d\n", value);exit(EXIT_SUCCESS);
}

3.4 示例

下面的 shell 会话日志演示了如何使用本章中到目前为止给出的各个程序。首先创建了一个初始值为零的信号量,然后在后台启动一个递减这个信号量的程序。

$ ./psem_create -c /demo 600 0
$ ./psem_wait /demo &
[1] 3498686

后台命令将会阻塞,这是因为信号量的当前值为 0,从而无法递减这个信号量。接着获取这个信号量的值。

$ ./psem_getvalue /demo
0

从上面可以看到值 0。在其它系统上可能会看到值−1,表示存在一个进程正在等待这个信号量。
接着执行一个命令来递增这个信号量,这将会导致后台程序中被阻塞的 sem_wait()调用完成执行。

$ ./psem_post /demo
3498686 sem_wait() succeeded
[1]+  Done                    ./psem_wait /demo

(上面输出中的最后一行表明 shell 提示符会与后台作业的输出混合在一起。)
按下回车后就能看到下一个shell提示符,这也会导致shell报告已终止的后台作业的信息。接着在信号量上执行后续的操作。

$ ./psem_post /demo
$ ./psem_getvalue /demo
1
$ ./psem_unlink /demo

4 未命名信号量

未命名信号量(也被称为基于内存的信号量)是类型为 sem_t 并存储在应用程序分配的内存中的变量。通过将这个信号量放在由几个进程或线程共性的内存区域中就能够使这个信号量对这些进程或线程可用。

操作未命名信号量所使用的函数与操作命名信号量使用的函数是一样的(sem_wait()、sem_post()以及 sem_getvalue()等)。此外,还需要用到另外两个函数(这些函数不应该被应用到命名信号量上)。

  • sem_init()函数对一个信号量进行初始化并通知系统该信号量会在进程间共享还是在单个进程中的线程间共享。
  • sem_destroy(sem)函数销毁一个信号量。

未命名与命名信号量对比
使用未命名信号量之后就无需为信号量创建一个名字了,这种做法在下列情况中是比较有用的。

  • 在线程间共享的信号量不需要名字。将一个未命名信号量作为一个共享(全局或堆上的)变量自动会使之对所有线程可访问。
  • 在相关进程间共享的信号量不需要名字。如果一个父进程在一块共享内存区域中(如一个共享匿名映射)分配了一个未命名信号量,那么作为 fork()操作的一部分,子进程会自动继承这个映射,从而继承这个信号量。
  • 如果正在构建的是一个动态数据结构(如二叉树),并且其中的每一项都需要一个关联的信号量,那么最简单的做法是在每一项中都分配一个未命名信号量。为每一项打开一个命名信号量需要为如何生成每一项中的信号量名字(唯一的)和管理这些名字设计一个规则(如当不再需要它们时就对它们进行断开链接操作)。

4.1 初始化一个未命名信号量

sem_init()函数使用 value 中指定的值来对 sem 指向的未命名信号量进行初始化。

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

pshared 参数表明这个信号量是在线程间共享 or 进程间共享

  • 如果 pshared 等于 0,那么信号量将会在调用进程中的线程间进行共享。在这种情况下,sem 通常被指定成一个全局变量的地址或分配在堆上的一个变量的地址。线程共享的信号量具备进程持久性,它在进程终止时会被销毁。
  • 如果 pshared 不等于 0,那么信号量将会在进程间共享。在这种情况下,sem 必须是共享内存区域(一个 POSIX 共享内存对象、一个使用 mmap()创建的共享映射)中的某个位置的地址。信号量的持久性与它所处的共享内存的持久性是一样的。(通过其中大部分技术创建的共享内存区域具备内核持久性。但共享匿名映射是一个例外,只要存在一个进程维持着这种映射,那么它就一直存在下去。)由于通过 fork()创建的子进程会继承其父进程的内存映射,因此进程共享的信号量会被通过 fork()创建的子进程继承,这样父进程和子进程也就能够使用这些信号量来同步它们的动作了。

之所以需要 pshared 参数是因为下列原因。

  • 一些实现不支持进程间共享的信号量。在这些系统上为 pshared 指定一个非零值会导致 sem_init()返回一个错误。Linux 直到内核 2.6 以及 NPTL 线程化技术的出现之后才开始支持未命名的进程间共享的信号量。
  • 在同时支持进程间共享信号量和线程间共享信号量的实现上,指定采用何种共享方式是有必要的,因为系统必须要执行特殊的动作来支持所需的共享方式。提供此类信息还使得系统能够根据共享的种类来执行优化工作。

未命名信号量不存在相关的权限设置(即 sem_init()中并不存在在 sem_open()中所需的mode 参数)。对一个未命名信号量的访问将由进程在底层共享内存区域上的权限来控制。
与命名信号量一样,在地址通过传入 sem_init()的 sem 参数指定的 sem_t 变量的副本上执行操作的结果是未定义的,因此应该总是只在“最初的”信号量上执行操作。

示例程序
可以使用互斥体来保护一个存在两个线程访问同一个全局变量的临界区的程序。但下面程序使用一个未命名线程共享的信号量解决了同样的问题。

$ ./thread_incr_psem
glob = 20000000
/* thread_incr_psem.cUse a POSIX unnamed semaphore to synchronize access by two threads toa global variable.See also thread_incr.c and thread_incr_mutex.c.使用一个 POSIX 未命名信号量来保护对全局变量的访问 
*/
#include <semaphore.h>
#include <pthread.h>
#include "tlpi_hdr.h"static int glob = 0;
static sem_t sem;static void *                   /* Loop 'arg' times incrementing 'glob' */
threadFunc(void *arg)
{int loops = *((int *) arg);int loc, j;for (j = 0; j < loops; j++) {if (sem_wait(&sem) == -1)errExit("sem_wait");loc = glob;loc++;glob = loc;if (sem_post(&sem) == -1)errExit("sem_post");}return NULL;
}int
main(int argc, char *argv[])
{pthread_t t1, t2;int loops, s;loops = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-loops") : 10000000;/* Initialize a semaphore with the value 1 */if (sem_init(&sem, 0, 1) == -1)errExit("sem_init");/* Create two threads that increment 'glob' */s = pthread_create(&t1, NULL, threadFunc, &loops);if (s != 0)errExitEN(s, "pthread_create");s = pthread_create(&t2, NULL, threadFunc, &loops);if (s != 0)errExitEN(s, "pthread_create");/* Wait for threads to terminate */s = pthread_join(t1, NULL);if (s != 0)errExitEN(s, "pthread_join");s = pthread_join(t2, NULL);if (s != 0)errExitEN(s, "pthread_join");printf("glob = %d\n", glob);exit(EXIT_SUCCESS);
}

4.2 销毁一个未命名信号量

sem_destroy()函数将销毁信号量 sem,其中 sem 必须是一个之前使用 sem_init()进行初始化的未命名信号量。只有在不存在进程或线程在等待一个信号量时才能够安全销毁这个信号量。

#include <semaphore.h>
int sem_destroy(sem_t *sem);

当使用 sem_destroy()销毁了一个未命名信号量之后就能够使用 sem_init()来重新初始化这个信号量了。
一个未命名信号量应该在其底层的内存被释放之前被销毁。例如,如果信号量一个自动分配的变量,那么在其宿主函数返回之前就应该销毁这个信号量。如果信号量位于一个 POSIX共享内存区域中,那么在所有进程都使用完这个信号量以及在使用 shm_unlink()对这个共享内存对象执行断开链接操作之前应该销毁这个信号量。
在一些实现上,省略 sem_destroy()调用不会导致问题的发生,但在其他实现上,不调用sem_destroy()会导致资源泄露。可移植的应用程序应该调用 sem_destroy()以避免此类问题的发生。

5 POSIX 信号量与 Pthreads 互斥体对比

POSIX 信号量和 Pthreads 互斥体都可以用来同步同一个进程中的线程的动作,并且它们的性能也是相近的。然而互斥体通常是首选方法,因为互斥体的所有权属性能够确保代码具有良好的结构性(只有锁住互斥体的线程才能够对其进行解锁)。与之形成对比的是,一个线程能够递增一个被另一个线程递减的信号量。这种灵活性会导致产生结构糟糕的同步设计。
(正是因为这个原因,信号量有时候会被称为并发式编程中的“goto”。)

互斥体在一种情况下是不能用在多线程应用程序中的,在这种情况下信号量可能就成了一种首选方法了。由于信号量是异步信号安全的,因此在一个信号处理器中可以使用 sem_post()函数来与另一个线程进行同步。而信号量就无法完成这项工作,因为操作互斥体的 Pthreads 函数不是异步信号安全的。然而通常处理异步信号的首选方法是使用sigwaitinfo()(或类似的函数)来接收这些信号,而不是使用信号处理器,因此信号量比互斥体在这一点上的优势很少有机会发挥出来。

6 信号量的限制

SUSv3 为信号量定义了两个限制。

  • SEM_NSEMS_MAX
    这是一个进程能够拥有的 POSIX 信号量的最大数目。SUSv3 要求这个限制至少为 256。在 Linux 上,POSIX 信号量数目实际上会受限于可用的内存。
  • SEM_VALUE_MAX
    这是一个 POSIX 信号量值能够取的最大值。信号量的取值可以为 0 到这个限制之间的任意一个值。SUSv3 要求这个限制至少为 32767,Linux 实现允许这个值最大为 INT_MAX(在Linux/x86-32 上是 2147483647)。

相关文章:

Linux/AndroidOS中进程间的通信线程间的同步 - 信号量

1 概述 本文将介绍 POSIX 信号量&#xff0c;它允许进程和线程同步对共享资源的访问。有两种类型的 POSIX 信号量&#xff1a; 命名信号量&#xff1a;这种信号量拥有一个名字。通过使用相同的名字调用 sem_open()&#xff0c;不相关的进程能够访问同一个信号量。未命名信号量…...

精益数据分析(46/126):深入剖析用户生成内容(UGC)商业模式

精益数据分析&#xff08;46/126&#xff09;&#xff1a;深入剖析用户生成内容&#xff08;UGC&#xff09;商业模式 在创业与数据分析的征程中&#xff0c;每一种商业模式都蕴含着独特的价值与挑战。今天&#xff0c;我们依旧怀揣着共同进步的信念&#xff0c;深入研读《精益…...

vue +xlsx+exceljs 导出excel文档

实现功能&#xff1a;分标题行导出数据过多&#xff0c;一个sheet表里表格条数有限制&#xff0c;需要分sheet显示。 步骤1:安装插件包 npm install exceljs npm install xlsx 步骤2&#xff1a;引用包 import XLSX from xlsx; import ExcelJS from exceljs; 步骤3&am…...

Android 10.0 SharedPreferences in credential encrypted storage are not avai

1.前言 在10.0的系统rom定制化开发中,在开机的过程中,由于某些应用在开机解锁阶段就开始访问查询短信和联系人等功能,所以 会出现抛异常的情况出现,接下来分析下相关的情况,然后来解决这些问题 2.SharedPreferences in credential encrypted storage are not available …...

面试高频算法:最长回文子串

题目&#xff1a;5. 最长回文子串 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 回文&#xff1a;如果字符串向前和向后读都相同&#xff0c;则它满足回文性&#xff1b;子串&#xff1a;子字符串 是字符串中连续的非空字符序列。 示例 1&#xff1a; 输入&…...

RDK X5 交叉编译OSS\QT\opencv\openssl

RDK X5 交叉编译环境配置 1 资源2 使用vm安装Ubuntu22.043 安装依赖4 安装ide5 下载交叉编译工具6 编译oss库6.1 设置临时环境变量6.2 编译arm版本的openssl6.2 编译arm版本的curl6.1 下载oss源码6.1.1 创建arm-toolchain.cmake6.1.2 修改CMakeLists.txt6.1.3 编译 7 编译openc…...

Python cv2边缘检测与轮廓查找:从理论到实战

在计算机视觉领域&#xff0c;边缘检测与轮廓查找是图像分析的核心技术。本文将结合OpenCV库&#xff08;cv2模块&#xff09;&#xff0c;从理论原理到代码实战&#xff0c;系统讲解如何通过Python实现这两个关键操作。 一、基础概念解析 1.1 边缘检测的本质 边缘是图像中灰…...

5月7日星期三今日早报简报微语报早读

5月7日星期三&#xff0c;农历四月初十&#xff0c;早报#微语早读。 1、1101名优秀运动员拟保送&#xff0c;全红婵、黄雨婷、盛李豪在列&#xff1b; 2、世界羽联主席巴达玛&#xff1a;中国组织赛事的能力无与伦比&#xff1b; 3、中国首位、亚洲首位&#xff01;赵心童夺…...

智慧医院的可视化变革:可视化工具助力数字化转型

在科技飞速发展的当下&#xff0c;智慧医院已从概念逐步落地&#xff0c;深刻改变着传统医疗模式。它借助互联网、数字孪生及人工智能等前沿技术&#xff0c;在医疗服务领域掀起革新&#xff0c;涵盖面向医务人员的“智慧医疗”、面向患者的“智慧服务”以及面向医院的“智慧管…...

python+open3d选择点云上的某个点并获取其对应三维坐标

👑主页:吾名招财 👓简介:工科学硕,研究方向机器视觉,爱好较广泛… ​💫签名:面朝大海,春暖花开! python+open3d选择点云上的某个点并获取其对应三维坐标 1,引言2,效果展示3,点云获取4,程序1,引言 有时候我们只想在点云上获取某个目标的具体坐标,通过程序根据…...

ROS第十三梯:RViz+Marker——自定义几何形状可视化

1)概述 在ROS(Robot Operating System)中,Marker是一种用于在RViz(Robot Visualization)中显示自定义几何形状和注释的工具。Marker是通过visualization_msgs/Marker消息类型发布的。可以在RViz中以各种形式(如点、线、文本、立方体等)显示数据。 2)主要消息格…...

Java高频面试之并发编程-13

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天又来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;详解原子性、可见性、有序性 在并发编程中&#xff0c;原子性&#xff08;Atomicity&#xff09;、可见性&#xff08;…...

WSL 的 Ubuntu 子系统中启用图形化界面

sudo chmod w /home sudo apt update sudo apt install cifs-utils 1. 选择合适的 X 服务器 在 Windows 系统上&#xff0c;需要安装一个 X 服务器来处理 WSL 中 Ubuntu 的图形显示。常用的 X 服务器有 VcXsrv 和 X410&#xff0c;这里以 VcXsrv 为例&#xff1a; 从VcXsrv 官…...

项目模拟实现消息队列第二天

消息应答的模式 1.自动应答: 消费者把这个消息取走了&#xff0c;就算是应答了&#xff08;相当于没有应答) 2.手动应答: basicAck方法属于手动应答(消费者需要主动调用这个api进行应答) 小结 1.需要实现生产者,broker server&#xff0c;消费者这三个部分的 2.针对生产者和消费…...

MySQL OCP和Oracle OCP怎么选?

近期oracle 为庆祝 MySQL 数据库发布 30 周年&#xff0c;Oracle 官方推出限时福利&#xff1a;2025 年 4 月 20 日至 7 月 31 日期间&#xff0c;所有人均可免费报考 MySQL OCP&#xff08;Oracle Certified Professional&#xff09;认证考试&#xff08;具体可查看MySQL OCP…...

SR触发器为什么能够消抖

SR触发器&#xff08;Set-Reset触发器&#xff09;能够用于**消抖&#xff08;Debounce&#xff09;**&#xff0c;主要是因为它的双稳态特性和对输入信号的锁定能力。机械开关&#xff08;如按键、拨动开关&#xff09;在闭合或断开时&#xff0c;由于金属触点的弹性&#xff…...

2025ISCC练武校级赛部分题解WP

Web 战胜卞相壹 <!-- 路过的酒罐王柯洁九段说&#xff1a; --> <!-- 会叠棋子有什么用&#xff01;你得在棋盘内战胜他&#xff01;我教你个定式&#xff0c;要一直记得&#xff01;一直&#xff01; --> <!-- SGF B[ae];B[ce];B[df];B[cg];B[ag];B[ai];B[ci];…...

Microsoft Azure 在印度尼西亚区域正式上线

微软正式宣布&#xff0c;其首个落地印度尼西亚的云区域——Indonesia Central 已全面上线并正式投入使用&#xff01;这一区域精心设置了三个可用性区&#xff08;Availability Zones&#xff09;&#xff0c;每个可用性区均配备独立的电源、冷却系统以及网络设施&#xff0c;…...

day18 python聚类分析对数据集模型性能影响

聚类后的分析&#xff1a;推断簇的类型 知识点回顾&#xff1a; 推断簇含义的2个思路&#xff1a;先选特征和后选特征通过可视化图形借助ai定义簇的含义科研逻辑闭环:通过精度判断特征工程价值 作业&#xff1a;参考示例代码对心脏病数据集采取类似操作&#xff0c;并且评估特征…...

vue3的新特性

vue2 data属性和方法名散落于各个位置&#xff0c;量大了不好找 顺序变了&#xff0c;script在最前面 setup vue3中不用this&#xff0c;setup的执行时期比beforeCreate还要早&#xff0c;所以不要用this setup中写代码的特点 必须要有return&#xff0c;才能在上面使用 什么…...

NX二次开发——BlockUI 弹出另一个BlockUI对话框

最近在研究&#xff0c;装配体下自动导出BOM表格中需要用到BlockUI 弹出另一个BlockUI对话框。通过对网上资料进行整理总结&#xff0c;具体如下&#xff1a; 1、明确主对话框、子对话框1和子对话框2 使用BlockUI创建.cpp和.hpp文件&#xff0c;dlx文件内容如下所示 主对话框…...

《Overlapping Experiment Infrastructure: More, Better, Faster》论文阅读笔记

文章目录 1 背景2 三个核心概念3 Launch层&#xff1a;特性发布的专用机制4 流量分发策略和条件筛选4.1 四种流量分发类型4.2 条件筛选机制 5 工具链与监控体系6 实验设计原则7 培训参考与推荐 1 背景 谷歌&#xff08;Google&#xff09;以数据驱动著称&#xff0c;几乎所有可…...

【Machine Learning Q and AI 读书笔记】- 05 利用数据减少过拟合现象

Machine Learning Q and AI 中文译名 大模型技术30讲&#xff0c;主要总结了大模型相关的技术要点&#xff0c;结合学术和工程化&#xff0c;对LLM从业者来说&#xff0c;是一份非常好的学习实践技术地图. 本文是Machine Learning Q and AI 读书笔记的第5篇&#xff0c;对应原…...

前端面试测试题目(一)

一、Vue的双向绑定机制&#xff08;v-model底层实现原理&#xff09; Vue的双向绑定核心由 响应式系统 和 指令语法糖 共同实现&#xff0c;具体原理如下&#xff1a; 响应式系统 Vue通过数据劫持和依赖收集实现数据变化到视图的同步&#xff1a; • 数据劫持&#xff1a;在Vue…...

最优化方法Python计算:无约束优化应用——线性回归分类器

一、线性回归分类器 假设样本数据为 ( x i , y i ) (\boldsymbol{x}_i, y_i) (xi​,yi​)&#xff0c;其中 i 1 , 2 , … , m i 1, 2, \dots, m i1,2,…,m。标签 y i y_i yi​ 取值于 k k k 个整数 { 1 , 2 , … , k } \{1, 2, \dots, k\} {1,2,…,k}&#xff0c;从而构…...

【汇正自控阀门集团】签约智橙PLM,智橙助泵阀“以国代进”

签约智橙&#xff0c;汇正阀门的“以国代进”举措 随着阀门市场竞争日益激烈、市场需求日益多样化&#xff0c;无论是出口海外、以国代进&#xff0c;还是进军新能源、造船、油气等投资景气的下游市场&#xff0c;阀门企业能否在快速迭代产品、保持技术领先的同时&#xff0c;…...

【macOS】iTerm2介绍

iTerm2 和 iTerm 是 macOS 上两个不同的终端模拟器&#xff0c;虽然名字相似&#xff0c;但它们是两个独立的项目&#xff0c;且 iTerm2 是 iTerm 的现代化继承者。以下是它们的核心区别和演进关系&#xff1a; 1. 历史背景 项目诞生时间状态开发者iTerm2002 年已停止维护Greg…...

2025年五一假期旅游市场新趋势:理性消费、多元场景与科技赋能

2025年五一假期&#xff0c;国内旅游市场再次迎来爆发式增长&#xff0c;官方数据显示&#xff0c;假期期间国内出游人次达3.14亿&#xff0c;游客总消费1802.69亿元。尽管数据规模亮眼&#xff0c;但深入分析可发现&#xff0c;旅游市场正经历结构性变革——消费行为趋于理性、…...

第3章 模拟法

3.1 模拟法概述 模拟法设计思想 模拟法通过将现实问题抽象成计算机可识别的符号与操作&#xff0c;按逻辑顺序“模拟”其过程&#xff0c;从而得到结果&#xff1b;它不依赖复杂公式或高深技巧&#xff0c;只需理清问题背景与实现步骤即可。 示例&#xff1a;鸡兔同笼问题 题…...

16.状态模式:思考与解读

原文地址:状态模式&#xff1a;思考与解读 更多内容请关注&#xff1a;深入思考与解读设计模式 引言 在开发软件系统时&#xff0c;特别是当对象的行为会随着状态的变化而变化时&#xff0c;系统往往会变得复杂。你是否遇到过这样的情况&#xff1a;一个对象的行为在不同的状…...

ActiveMQ 源码剖析:消息存储与通信协议实现(二)

四、KahaDB 消息存储实现细节 &#xff08;一&#xff09;存储原理分析 KahaDB 作为 ActiveMQ 从 5.4 版本开始的默认消息存储引擎&#xff0c;其基于日志文件的存储原理具有独特的设计和优势 。在 KahaDB 的存储目录&#xff08;如${activemq.data}/kahadb&#xff09;下&am…...

明远智睿SD2351核心板:工业AIoT时代的创新引擎

在当今工业互联网飞速发展的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;与物联网&#xff08;IoT&#xff09;的深度融合正以前所未有的态势重塑着传统制造业的格局。从自动化生产线的精准控制到智能仓储的高效管理&#xff0c;从设备运行的实时监测到产品质量的严格…...

iPhone 和 Android 在日期格式方面的区别

整篇文章由iPhone 和 Android 在日期格式方面有所不同引起,大致介绍了,两种时间标准,以及在 JavaScript 下的格式转换方法。 Unix 时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。 iPhone 和 Android 在日期格式方面有所不同。其中,iPhone(iOS)使…...

使用VSCode在Windows 11上编译运行项目

使用VSCode在Windows 11上编译运行项目 VSCode是一个功能强大的跨平台代码编辑器&#xff0c;可以很好地支持C/C项目开发。以下是使用VSCode在Windows 11上编译运行此项目的详细步骤。 1. 安装VSCode 访问VSCode官网下载并安装VSCode安装完成后&#xff0c;启动VSCode 2. 安…...

边缘计算,运维架构从传统的集中式向分布式转变

在当今数字化时代&#xff0c;边缘计算的崛起正在改变着运维的格局。随着物联网、5G 等技术的快速发展&#xff0c;越来越多的数据和应用正在向边缘设备迁移&#xff0c;这给运维团队带来了新的挑战和机遇。 一、边缘计算崛起带来的运维挑战 边缘计算将计算和数据存储靠近数据…...

【基础篇】prometheus热更新解读

文章目录 本篇内容讲解热更新参数源码解读本篇总结本篇内容讲解 prometheus热更新源码解读 热更新参数 –web.enable-lifecycle : 代表开启热更新配置 修改配置文件发http请求# curl -X POST -vvv localhost:9090/-/reload * About to connect() to localhost port 9090 (…...

为了结合后端而学习前端的学习日志(1)——纯CSS静态卡片案例

前端设计专栏 使用纯CSS创建简洁名片卡片的学习实践 在这篇技术博客中&#xff0c;我将分享我的前端学习过程&#xff0c;如何使用纯HTML和CSS创建一个简洁美观的名片式卡片&#xff0c;就像我博客首页展示的那样。这种卡片设计非常适合作为个人简介、产品展示或团队成员介绍…...

汽车服务小程序功能点开发

汽车养护服务功能 智能保养预约&#xff1a;根据车辆品牌、型号及行驶里程&#xff0c;自动推荐保养项目&#xff0c;支持线上预约 4S 店或合作维修厂&#xff0c;选择服务时间与地点。故障诊断与维修&#xff1a;车主上传车辆故障现象&#xff0c;系统智能初步诊断&#xff0…...

SENSE2020BSI sCMOS科学级相机主要参数及应用场景

SENSE2020BSI sCMOS科学级相机是一款面向宽光谱成像需求的高性能科学成像设备&#xff0c;结合了背照式&#xff08;Back-Side Illuminated, BSI&#xff09;CMOS技术与先进信号处理算法&#xff0c;适用于天文观测、生物医学成像、工业检测等领域。以下是其核心特点及技术细节…...

《汽车噪声控制》复习重点

题型 选择 填空 分析 计算 第一章 噪声定义 不需要的声音&#xff0c;妨碍正常工作、学习、生活&#xff0c;危害身体健康的声音&#xff0c;统称为噪声 噪声污染 与大气污染、水污染并称现代社会三大公害 声波基本概念 定义 媒质质点的机械振动由近及远传播&am…...

物流无人机结构与载货设计分析!

一、物流无人机的结构与载货设计模块运行方式 1.结构设计特点 垂直起降与固定翼结合&#xff1a;针对复杂地形&#xff08;如山区、城市&#xff09;需求&#xff0c;采用垂直起降&#xff08;VTOL&#xff09;与固定翼结合的复合布局&#xff0c;例如“天马”H型无人机&am…...

docker创建一个centOS容器安装软件(以宝塔为例)的详细步骤

备忘&#xff1a;后续偶尔忘记了docker虚拟机与宿主机的端口映射关系&#xff0c;来这里查看即可&#xff1a; docker run -d \ --name baota \ --privilegedtrue \ -p 8888:8888 \ -p 8880:80 \ -p 8443:443 \ -p 8820:20 \ -p 8821:21 \ -v /home/www:/www/wwwroot \ centos…...

D盘出现不知名文件

各位大佬&#xff0c;电脑D盘去年还干干净净的&#xff0c;后来突然就出现了所圈部分的几个不知名文件&#xff0c;请问这是什么东西&#xff1f;是否可以删除&#xff1f;...

Rust 中 Arc 的深度分析:从原理到性能优化实践

在 Rust 的并发编程中&#xff0c;Arc&#xff08;Atomic Reference Counted&#xff09; 是一个非常关键的智能指针类型&#xff0c;用于在多个线程之间共享数据的所有权。它通过原子操作维护引用计数&#xff0c;确保在多线程环境下安全地管理堆内存资源。然而&#xff0c;很…...

qsort函数

在本篇中&#xff0c;将深入了解qsort函数的用法。 1.qsort函数的基础知识 该函数是用来排序的&#xff0c;这是一个可以直接用来排序数据的库函数&#xff08;#include<stdlib.h>&#xff09;&#xff0c;底层使用的是快速排序的方式。 常见的排序方式有&#xff1a; …...

01 一文了解大数据存储框架:数据库、数据仓库、数据集市、数据网格、数据湖、数据湖仓

1. 大数据存储框架 1.1 定义 数据库&#xff08;Database&#xff09;&#xff1a;数据库是按照数据结构来组织、存储和管理数据的仓库&#xff0c;是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。数据仓库&#xff08;Data Warehouse&#xff…...

QT —— QWidget(2)

QT —— QWidget&#xff08;2&#xff09; windowTitlewindowIconQt 资源系统 (qrc 机制) 详解基本概念使用方法1. 创建 .qrc 文件 设置背景windowOpacity 我们今天继续来学习QWidget&#xff0c;如果大家上一次的博客还没有看过&#xff0c;可以点击这里&#xff1a; https:/…...

微信小程序预览文件 兼容性苹果

uni.request({url: url,method: GET,header: {Authorization: token,responseType: blob,},responseType: "arraybuffer",success: (res) > {uni.hideLoading()const fs wx.getFileSystemManager(); //获取全局唯一的文件管理器let index url.lastIndexOf("…...

QT:qt5调用打开exe程序并获取调用按钮控件实例2025.5.7

为实现在 VS2015 的 Qt 开发环境下打开外部 exe&#xff0c;列出其界面按钮控件的序号与文本名&#xff0c;然后点击包含特定文本的按钮控件。以下是更新后的代码&#xff1a; #include <QCoreApplication> #include <QProcess> #include <QDebug> #include…...

Flink + Kafka 数据血缘追踪与审计机制实战

一、引言 在实时数据系统中,“我的数据从哪来?去往何处?” 是业务方最关心的问题之一。 尤其在以下场景下: 📉 金融风控:模型出现预警,需回溯数据源链路。 🧾 合规审计:监管要求提供数据全流程路径。 🛠 运维排查:Kafka Topic 数据乱序或错发后快速定位来源。 …...