深圳福田建网站个人网站做导购可以吗

web/2025/9/27 18:53:36/文章来源:
深圳福田建网站,个人网站做导购可以吗,现在装网线多少钱一年,合肥专业做网站的Linux高性能服务器编程 本文是读书笔记#xff0c;如有侵权#xff0c;请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第14章 多线程编程14.1 Linux线程概述14…Linux高性能服务器编程 本文是读书笔记如有侵权请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第14章 多线程编程14.1 Linux线程概述14.1.1 线程模型14.1.2 Linux线程库 14.2 创建线程和结束线程14.3 线程属性14.4 POSIX信号量14.5 互斥锁14.5.1 互斥锁基础API14.5.2 互斥锁属性14.5.3 死锁举例 14.6 条件变量14.7 线程同步机制包装类14.8 多线程环境14.8.1可重入函数14.8.2 线程和进程14.8.3 线程和信号 多线程编程注意事项后记 第14章 多线程编程 早期Linux不支持线程直到1996年Xavier Leroy等人才开发出第一个基本符合 POSIX标准的线程库LinuxThreads。但LinuxThreads效率低而且问题很多。自内核2.6开始Linux才真正提供内核级的线程支持并有两个组织致力于编写新的线程库NGPT (Next Generation POSIX Threads)和NPTL(Native POSIX Thread Library)。不过前者在 2003年就放弃了因此新的线程库就称为NPTL。NPTL比LinuxThreads效率高且更符 合POSIX规范所以它已经成为glibc的一部分。本书所有线程相关的例程使用的线程库都是NPTL。 本章要讨论的线程相关的内容都属于POSIX线程(简称pthread)标准而不局限于NPTL实现具体包括 创建线程和结束线程。读取和设置线程属性。POSIX线程同步方式POSIX信号量、互斥锁和条件变量。 在本章的最后我们还将介绍在Linux环境下库函数、进程、信号与多线程程序之间 的相互影响。 14.1 Linux线程概述 14.1.1 线程模型 线程是程序中完成一个独立任务的完整执行序列即一个可调度的实体。根据运行环境和调度者的身份线程可分为内核线程和用户线程。内核线程在有的系统上也称为LWP (Light Weight Process轻量级进程运行在内核空间由内核来调度用户线程运行在用户空间由线程库来调度。当进程的一个内核线程获得CPU的使用权时它就加载并运行一个用户线程。可见内核线程相当于用户线程运行的“容器”。一个进程可以拥有M个内核线程和N个用户线程其中M≤N。并且在一个系统的所有进程中M和N的比值都是固定的。按照M:N的取值线程的实现方式可分为三种模式完全在用户空间实现、完全由内核调度和双层调度two level scheduler。 完全在用户空间实现的线程无须内核的支持内核甚至根本不知道这些线程的存在。线程库负责管理所有执行线程比如线程的优先级、时间片等。线程库利用longjmp来切换线程的执行使它们看起来像是“并发”执行的。但实际上内核仍然是把整个进程作为最小单位来调度的。换句话说一个进程的所有执行线程共享该进程的时间片它们对外表现出相同的优先级。因此对这种实现方式而言N1即M个用户空间线程对应1个内核线程 而该内核线程实际上就是进程本身。完全在用户空间实现的线程的优点是创建和调度线程都无须内核的干预因此速度相当快。并且由于它不占用额外的内核资源所以即使一个进程创建了很多线程也不会对系统性能造成明显的影响。其缺点是对于多处理器系统一个进程的多个线程无法运行在不同的CPU上因为内核是按照其最小调度单位来分配CPU 的。此外线程的优先级只对同一个进程中的线程有效比较不同进程中的线程的优先级没有意义。早期的伯克利UNIX线程就是采用这种方式实现的。 完全由内核调度的模式将创建、调度线程的任务都交给了内核运行在用户空间的线 程库无须执行管理任务这与完全在用户空间实现的线程恰恰相反。二者的优缺点也正好互换。较早的Linux内核对内核线程的控制能力有限线程库通常还要提供额外的控制能力尤其是线程同步机制不过现代Linux内核已经大大增强了对线程的支持。完全由内核调度的这种线程实现方式满足MN1:1即1个用户空间线程被映射为1个内核线程。 双层调度模式是前两种实现模式的混合体内核调度M个内核线程线程库调度N个用户线程。这种线程实现方式结合了前两种方式的优点不但不会消耗过多的内核资源而 且线程切换速度也较快同时它可以充分利用多处理器的优势。 14.1.2 Linux线程库 Linux上两个最有名的线程库是LinuxThreads和NPTL它们都是采用11的方式实现的。由于LinuxThreads在开发的时候Linux内核对线程的支持还非常有限所以其可用性、 稳定性以及POSIX兼容性都远远不及NPTL。现代Linux上默认使用的线程库是NPTL。用 户可以使用如下命令来查看当前系统上所使用的线程库 $ getconf GNU_LIBPTHREAD_VERSION NPTL 2.14.90LinuxThreads 线程库的内核线程是用clone系统调用创建的进程模拟的。clone系统调用和fork系统调用的作用类似创建调用进程的子进程。不过我们可以为clone系统调用指定 CLONE_THREAD标志这种情况下它创建的子进程与调用进程共享相同的虚拟地址空间、 文件描述符和信号处理函数这些都是线程的特点。不过用进程来模拟内核线程会导致很多语义问题比如 每个线程拥有不同的PID因此不符合POSIX规范。 Linux信号处理本来是基于进程的但现在一个进程内部的所有线程都能而且必须处理信号。 用户ID、组ID对一个进程中的不同线程来说可能是不一样的。 程序产生的核心转储文件不会包含所有线程的信息而只包含产生该核心转储文件的线程的信息。 由于每个线程都是一个进程因此系统允许的最大进程数也就是最大线程数。 LinuxThreads线程库一个有名的特性是所谓的管理线程。它是进程中专门用于管理其他工作线程的线程。其作用包括 系统发送给进程的终止信号先由管理线程接收管理线程再给其他工作线程发送同样的信号以终止它们。当终止工作线程或者工作线程主动退出时管理线程必须等待它们结束以避免僵尸进程。如果主线程先于其他工作线程退出则管理线程将阻塞它直到所有其他工作线程都结束之后才唤醒它。回收每个线程堆栈使用的内存。 管理线程的引入增加了额外的系统开销。并且由于它只能运行在一个CPU上所以LinuxThreads线程库也不能充分利用多处理器系统的优势。要解决LinuxThreads线程库的一系列问题不仅需要改进线程库最主要的是需要内核提供更完善的线程支持。因此Linux内核从2.6版本开始提供了真正的内核线程。新的 NPTL线程库也应运而生。相比LinuxThreadsNPTL的主要优势在于 内核线程不再是一个进程因此避免了很多用进程模拟内核线程导致的语义问题。摒弃了管理线程终止线程、回收线程堆栈等工作都可以由内核来完成。由于不存在管理线程所以一个进程的线程可以运行在不同的CPU上从而充分利用了多处理器系统的优势。线程的同步由内核来完成。隶属于不同进程的线程之间也能共享互斥锁因此可实现跨进程的线程同步。 chatGPT对NPTL线程库的介绍 NPTLNative POSIX Thread Library是Linux系统上的一种线程库用于支持POSIX线程pthread标准。它是Linux上的本地线程库被设计为提供更好的性能和可扩展性。NPTL 取代了之前的 LinuxThreads 库并成为Linux系统默认的线程库。 以下是关于 NPTL 的一些关键特性和设计思想 Kernel-Level Threads NPTL 使用内核级线程kernel-level threads这意味着每个线程在内核中都有一个对应的轻量级进程LWP。这样的设计使得线程的创建、销毁和切换更加高效同时允许多个线程并发执行。 线程本地存储Thread-Local StorageTLS NPTL 支持线程本地存储使每个线程都能够拥有自己的独立变量而不受其他线程的影响。这提高了线程的独立性和可靠性。 更好的性能 NPTL 的设计旨在提供更好的性能尤其是在多处理器系统上。通过使用更有效的线程创建和管理机制以及对多处理器的更好的支持NPTL 能够在多线程应用程序中发挥更好的性能。 兼容POSIX标准 NPTL 遵循POSIX线程标准这意味着使用 NPTL 编写的多线程程序可以在符合POSIX标准的系统上进行移植。 支持可扩展性 NPTL 提供了一种可扩展的线程模型允许在不同的硬件和系统架构上获得最佳性能。 可调整的线程栈大小 NPTL 允许动态调整线程的栈大小以适应不同类型的应用程序需求。 提供互斥锁、条件变量等同步原语 NPTL 提供了一套完整的同步原语包括互斥锁、条件变量等以帮助开发者编写线程安全的程序。 总体而言NPTL 为Linux系统上的多线程编程提供了一个强大、高效和可扩展的基础。由于其出色的性能和可靠性NPTL 已成为Linux上默认的线程库被广泛用于支持并发和并行编程。 14.2 创建线程和结束线程 下面我们讨论创建和结束线程的基础API。Linux系统上它们都定义在pthread.h头文 件中。 1.pthread_create 创建一个线程的函数是pthread_create。其定义如下 #include pthread.h int pthread_create( pthread_t* thread, const pthread_attr_t* attr,void* ( *start_routine )( void* ), void* arg );thread参数是新线程的标识符后续pthread_*函数通过它来引用新线程。其类型pthread_t的定义如下: #include bits/ pthreadtypes.h typedef unsigned long int pthread_t;可见,pthread_t是一个整型类型。实际上Linux上几乎所有的资源标识符都是一个整型数比如socket、各种System V IPC标识符等。attr参数用于设置新线程的属性。给它传递NULL表示使用默认线程属性。线程拥有众多属性我们将在后面详细讨论之。start_routine和arg参数分别指定新线程将运行的函数及其参数。 pthread_create成功时返回0,失败时返回错误码。一个用户可以打开的线程数量不能超过RLIMIT_NPROC软资源限制。此外系统上所有用户能创建的线程总数也不得超过/proc/sys/kernel/threads-max内核参数所定义的值。 2.pthread_exit 线程一旦被创建好内核就可以调度内核线程来执行 start_routine函数指针所指向的函数了。线程函数在结束时最好调用如下函数以确保安全、干净地退出 #include pthread.h void pthread_exit( void* retval );pthread_exit 函数通过retval参数向线程的回收者传递其退出信息。它执行完之后不会返回到调用者而且永远不会失败。 3.pthread_join 一个进程中的所有线程都可以调用pthread_join函数来回收其他线程(前提是目标线程是可回收的见后文)即等待其他线程结束这类似于回收进程的wait和waitpid系统调用。pthread_join的定义如下: #include pthread.h int pthread_join( pthread_t thread, void** retval );thread参数是目标线程的标识符retval参数则是目标线程返回的退出信息。该函数会一直阻塞直到被回收的线程结束为止。该函数成功时返回0失败则返回错误码。可能的错 误码如表14-1所示。 4.pthread_cancel 有时候我们希望异常终止一个线程即取消线程它是通过如下函数实现的 #include pthread.h int pthread_cancel( pthread_t thread );thread参数是目标线程的标识符。该函数成功时返回0失败则返回错误码。不过接收到取消请求的目标线程可以决定是否允许被取消以及如何取消这分别由如下两个函数完成 #include pthread.h int pthread_setcancelstate( int state, int *oldstate ); int pthread_setcanceltype( int type, int *oldtype );这两个函数的第一个参数分别用于设置线程的取消状态(是否允许取消和取消类型 如何取消第二个参数则分别记录线程原来的取消状态和取消类型。state参数有两个可选值 PTHREAD_CANCEL_ENABLE,允许线程被取消。它是线程被创建时的默认取消状态。OPTHREAD_CANCEL_DISABLE禁止线程被取消。这种情况下如果一个线程收到 取消请求则它会将请求挂起直到该线程允许被取消。 type参数也有两个可选值 PTHREAD_CANCEL_ASYNCHRONOUS,线程随时都可以被取消。它将使得接收到 取消请求的目标线程立即采取行动。PTHREAD_CANCEL_DEFERRED允许目标线程推迟行动直到它调用了下面几个 所谓的取消点函数中的一个pthread_ join、pthread_testcancel、pthread_cond_wait、 pthread_cond_timedwait、sem_wait和sigwait。根据POSIX标准其他可能阻塞的系 统调用比如read、wait也可以成为取消点。不过为了安全起见我们最好在可能会被取消的代码中调用 pthread_testcancel 函数以设置取消点。 pthread_setcancelstate和pthread_setcanceltype成功时返回0,失败则返回错误码。 14.3 线程属性 pthread_attr_t结构体定义了一套完整的线程属性如下所示 #include bits/ pthreadtypes.h #define __SIZEOF_PTHREAD_ATTR_T 36 typedef union {char __size[__SIZEOF_PTHREAD_ATTR_T]; long int __align; } pthread_attr_t;可见各种线程属性全部包含在一个字符数组中。线程库定义了一系列函数来操作 pthread_attr_t类型的变量以方便我们获取和设置线程属性。这些函数包括 下面我们详细讨论每个线程属性的含义 detachstate,线程的脱离状态。它有PTHREAD_CREATE_JOINABLE和PTHREAD_CREATE_DETACH两个可选值。前者指定线程是可以被回收的后者使调用线程脱离与进程中其他线程的同步。脱离了与其他线程同步的线程称为“脱离线程”。 脱离线程在退出时将自行释放其占用的系统资源。线程创建时该属性的默认值是 PTHREAD_CREATE_JOINABLE。此外,我们也可以使用pthread_detach函数直接将线程设置为脱离线程。 stackaddr和stacksize线程堆栈的起始地址和大小。一般来说我们不需要自己来管理线程堆栈因为Linux默认为每个线程分配了足够的堆栈空间一般是8MB。我 们可以使用ulimt -s命令来查看或修改这个默认值。 guardsize保护区域大小。如果guardsize大于0则系统创建线程的时候会在其堆栈的尾部额外分配guardsize字节的空间作为保护堆栈不被错误地覆盖的区 域。如果guardsize等于0则系统不为新创建的线程设置堆栈保护区。如果使用者通过pthread_attr_setstackaddr 或pthread_attr_setstack 函数手动设置线程的堆栈则 guardsize属性将被忽略。 schedparam,线程调度参数。其类型是sched_param结构体。该结构体目前还只有一 个整型类型的成员一sched_priority,该成员表示线程的运行优先级。 schedpolicy,线程调度策略。该属性有SCHED_FIFO、SCHED_RR和SCHED_OTHER三个可选值其中SCHED_OTHER是默认值。SCHED_RR表示采用轮转算法round-robin调度SCHED_FIFO表示使用先进先出的方法调度这两种调度方 法都具备实时调度功能但只能用于以超级用户身份运行的进程。 inheritsched是否继承调用线程的调度属性。该属性有PTHREAD_INHERIT_SCHED和PTHREAD_EXPLICIT_SCHED两个可选值。前者表示新线程沿用其创建者的线程调度参数这种情况下再设置新线程的调度参数属性将没有任何效果。后者表示调用者要明确地指定新线程的调度参数。 scope线程间竞争CPU的范围即线程优先级的有效范围。POSIX标准定义了该属性的PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS两个可选值前者表示目标线程与系统中所有线程一起竞争CPU的使用后者表示目标线程仅与其他隶属于同一进程的线程竞争CPU的使用。目前Linux只支持PTHREAD_SCOPE_ SYSTEM这一种取值。 14.4 POSIX信号量 和多进程程序一样多线程程序也必须考虑同步问题。pthread_join 可以看作一种简单的线程同步方式不过很显然它无法高效地实现复杂的同步需求比如控制对共享资源的独占式访问又抑或是在某个条件满足之后唤醒一个线程。接下来我们讨论3种专门用于线 程同步的机制POSIX信号量、互斥量和条件变量。 在Linux上信号量API有两组。一组是第13章讨论过的System V IPC信号量另外一组是我们现在要讨论的POSIX信号量。这两组接口很相似但不保证能互换。由于这两种信号量的语义完全相同因此我们不再赘述信号量的原理。POSIX信号量函数的名字都以sem_开头并不像大多数线程函数那样以pthread_开头。常用的POSIX信号量函数是下面5个 #include semaphore.h int sem_init( sem_t* sem, int pshared, unsigned int value ); int sem_destroy( sem_t* sem ); int sem_wait( sem_t* sem ); int sem_trywait( sem_t* sem ); int sem_post( sem_t* sem );这些函数的第一个参数sem指向被操作的信号量。 sem_init函数用于初始化一个未命名的信号量(POSIX信号量API支持命名信号量不 过本书不讨论它)。pshared参数指定信号量的类型。如果其值为0就表示这个信号量是当前进程的局部信号量否则该信号量就可以在多个进程之间共享。value参数指定信号量的初 始值。此外初始化一个已经被初始化的信号量将导致不可预期的结果。 sem_destroy函数用于销毁信号量以释放其占用的内核资源。如果销毁一个正被其他线程等待的信号量则将导致不可预期的结果。 sem wait函数以原子操作的方式将信号量的值减1。如果信号量的值为0,则sem_wait 将被阻塞直到这个信号量具有非0值。 sem_trywait与sem_wait函数相似,不过它始终立即返回而不论被操作的信号量是否具有非0值相当于sem_wait的非阻塞版本。当信号量的值非0时sem_trywait对信号量执行减1操作。当信号量的值为0时它将返回-1并设置errno为EAGAIN。 sem_post函数以原子操作的方式将信号量的值加1。当信号量的值大于0时其他正在调用 sem_wait等待信号量的线程将被唤醒。 上面这些函数成功时返回0失败则返回-1并设置errno。 14.5 互斥锁 互斥锁(也称互斥量可以用于保护关键代码段以确保其独占式的访问这有点像一 个二进制信号量。当进入关键代码段时我们需要获得互斥锁并将其加锁 这等价于二进制信号量的P操作当离开关键代码段时我们需要对互斥锁解锁以唤醒其他等待该互斥锁的线程这等价于二进制信号量的V操作。 14.5.1 互斥锁基础API POSIX互斥锁的相关函数主要有如下5个 #include pthread.h int pthread_mutex_init( pthread_mutex_t* mutex, constpthread_mutexattr_t* mutexattr ); int pthread_mutex_destroy( pthread_mutex_t* mutex ); int pthread_mutex_lock( pthread_mutex_t* mutex ); int pthread_mutex_trylock( pthread_mutex_t* mutex ); int pthread_mutex_unlock( pthread_mutex_t* mutex );这些函数的第一个参数mutex指向要操作的目标互斥锁,互斥锁的类型是pthread_ mutex_t结构体。 pthread_mutex_init 函数用于初始化互斥锁。mutexattr参数指定互斥锁的属性。如果将它设置为NULL则表示使用默认属性。我们将在下一小节讨论互斥锁的属性。除了这个函数外我们还可以使用如下方式来初始化一个互斥锁 pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;宏PTHREAD_MUTEX_INITIALIZER实际上只是把互斥锁的各个字段都初始化为0。 pthread_mutex_destroy函数用于销毁互斥锁以释放其占用的内核资源。销毁一个已经加锁的互斥锁将导致不可预期的后果。 pthread_mutex_lock函数以原子操作的方式给一个互斥锁加锁。如果目标互斥锁已经被锁上则pthread_mutex_lock 调用将阻塞直到该互斥锁的占有者将其解锁。 pthread_mutex_trylock与pthread_mutex_lock 函数类似不过它始终立即返回而不论被操作的互斥锁是否已经被加锁相当于pthread_mutex_lock的非阻塞版本。当目标互斥锁未被加锁时,pthread_mutex_trylock 对互斥锁执行加锁操作。当互斥锁已经被加锁时 pthread_mutex_trylock将返回错误码EBUSY。需要注意的是这里讨论的pthread_mutex lock和pthread_mutex_trylock的行为是针对普通锁而言的。后面我们将看到对于其他类型的锁而言这两个加锁函数会有不同的行为。 pthread_mutex_unlock函数以原子操作的方式给一个互斥锁解锁。如果此时有其他线程 正在等待这个互斥锁则这些线程中的某一个将获得它。 上面这些函数成功时返回0失败则返回错误码。 14.5.2 互斥锁属性 pthread_mutexattr_t结构体定义了一套完整的互斥锁属性。线程库提供了一系列函数来 操作pthread_mutexattr_t类型的变量以方便我们获取和设置互斥锁属性。这里我们列出其 中一些主要的函数 本书只讨论互斥锁的两种常用属性pshared和type。互斥锁属性pshared指定是否允许 跨进程共享互斥锁其可选值有两个 PTHREAD_PROCESS_SHARED。互斥锁可以被跨进程共享。 PTHREAD_PROCESS_PRIVATE。互斥锁只能被和锁的初始化线程隶属于同一个进程 的线程共享。 互斥锁属性type指定互斥锁的类型。Linux支持如下4种类型的互斥锁 PTHREAD_MUTEX_NORMAL普通锁。这是互斥锁默认的类型。当一个线程对一个普通锁加锁以后其余请求该锁的线程将形成一个等待队列并在该锁解锁后按优先级获得它。这种锁类型保证了资源分配的公平性。但这种锁也很容易引发问题一个线程如果对一个已经加锁的普通锁再次加锁将引发死锁对一个已经被其他线程加锁的普通锁解锁或者对一个已经解锁的普通锁再次解锁将导致不可预期的后果。 PTHREAD_MUTEX_ERRORCHECK检错锁。一个线程如果对一个已经加锁的检错锁再次加锁则加锁操作返回EDEADLK。对一个已经被其他线程加锁的检错锁解锁或者对一个已经解锁的检错锁再次解锁则解锁操作返回EPERM。 PTHREAD_MUTEX_RECURSIVE嵌套锁。这种锁允许一个线程在释放锁之前多次 对它加锁而不发生死锁。不过其他线程如果要获得这个锁则当前锁的拥有者必须执行相应次数的解锁操作。对一个已经被其他线程加锁的嵌套锁解锁或者对一个已经 解锁的嵌套锁再次解锁则解锁操作返回EPERM。 PTHREAD_MUTEX_DEFAULT默认锁。一个线程如果对一个已经加锁的默认锁再次加锁或者对一个已经被其他线程加锁的默认锁解锁或者对一个已经解锁的默认 锁再次解锁将导致不可预期的后果。这种锁在实现的时候可能被映射为上面三种锁之一。 14.5.3 死锁举例 使用互斥锁的一个噩耗是死锁。死锁使得一个或多个线程被挂起而无法继续执行而且这种情况还不容易被发现。前文提到在一个线程中对一个已经加锁的普通锁再次加锁将 导致死锁。这种情况可能出现在设计得不够仔细的递归函数中。另外如果两个线程按照不同的顺序来申请两个互斥锁也容易产生死锁如代码清单14-1所示。 14-1mutual_lock.c #include pthread.h #include unistd.h #include stdio.hint a 0; int b 0; pthread_mutex_t mutex_a; pthread_mutex_t mutex_b;// 子线程函数 void* another( void* arg ) {pthread_mutex_lock( mutex_b );printf( in child thread, got mutex b, waiting for mutex a\n );sleep( 5 );b;pthread_mutex_lock( mutex_a );b a;pthread_mutex_unlock( mutex_a );pthread_mutex_unlock( mutex_b );pthread_exit( NULL ); }int main() {pthread_t id;// 初始化互斥锁pthread_mutex_init( mutex_a, NULL );pthread_mutex_init( mutex_b, NULL );// 创建子线程pthread_create( id, NULL, another, NULL );// 主线程获取mutex_apthread_mutex_lock( mutex_a );printf( in parent thread, got mutex a, waiting for mutex b\n );sleep( 5 );a;// 主线程获取mutex_bpthread_mutex_lock( mutex_b );a b;// 释放互斥锁pthread_mutex_unlock( mutex_b );pthread_mutex_unlock( mutex_a );// 等待子线程结束pthread_join( id, NULL );// 销毁互斥锁pthread_mutex_destroy( mutex_a );pthread_mutex_destroy( mutex_b );return 0; }这段代码演示了一个典型的死锁Deadlock情况。在主线程和子线程中两个互斥锁 mutex_a 和 mutex_b 被获取的顺序相反导致了潜在的死锁情况。当主线程获取 mutex_a 并等待 mutex_b而子线程获取 mutex_b 并等待 mutex_a 时两者都无法继续执行造成死锁。 具体代码执行流程如下 主线程获取 mutex_a然后等待 mutex_b。子线程获取 mutex_b然后等待 mutex_a。由于两个线程都在等待对方持有的锁它们陷入了死锁状态。 死锁是多线程编程中需要小心避免的一种情况。解决死锁的一种方法是确保所有线程都以相同的顺序获取互斥锁。在这个例子中可以修改其中一个线程的互斥锁获取顺序以避免死锁的发生。 代码清单14-1中主线程试图先占有互斥锁mutex_a,然后操作被该锁保护的变量a, 但操作完毕之后主线程并没有立即释放互斥锁mutex_a,而是又申请互斥锁mutex_b,并在两个互斥锁的保护下操作变量a和b最后才一起释放这两个互斥锁与此同时子线程则按照相反的顺序来申请互斥锁mutex_a和mutex_b,并在两个锁的保护下操作变量a和 b。我们用sleep函数来模拟连续两次调用pthread_mutex_lock 之间的时间差以确保代码中的两个线程各自先占有一个互斥锁(主线程占有mutex_a,子线程占有mutex_b),然后等待另外一个互斥锁(主线程等待mutex_b子线程等待mutex_a)。这样两个线程就僵持住 了谁都不能继续往下执行从而形成死锁。如果代码中不加入sleep函数则这段代码或许总能成功地运行从而为程序留下了一个潜在的BUG。 14.6 条件变量 如果说互斥锁是用于同步线程对共享数据的访问的话那么条件变量则是用于在线程之间同步共享数据的值。条件变量提供了一种线程间的通知机制当某个共享数据达到某个值的时候唤醒等待这个共享数据的线程。条件变量的相关函数主要有如下5个 #include pthread.h int pthread_cond_init( pthread_cond_t* cond, const pthread_condattr_t* cond_attr); int pthread_cond_destroy( pthread_cond_t* cond); int pthread_cond_broadcast( pthread_cond_t* cond ); int pthread_cond_signal( pthread_cond_t* cond ); int pthread_cond_wait( pthread_cond_t* cond, pthread_mutex_t* mutex );这些函数的第一个参数cond指向要操作的目标条件变量条件变量的类型是pthread_ cond_t结构体。 pthread_cond_init 函数用于初始化条件变量。cond_attr参数指定条件变量的属性。如果将它设置为NULL则表示使用默认属性。条件变量的属性不多而且和互斥锁的属性类型相似所以我们不再赘述。除了pthread_cond_init 函数外我们还可以使用如下方式来初始 化一个条件变量 pthread_cond_t cond PTHREAD_COND_INITIALIZER;宏PTHREAD_COND_INITIALIZER实际上只是把条件变量的各个字段都初始化为0。 pthread_cond_destroy函数用于销毁条件变量以释放其占用的内核资源。销毁一个正在被等待的条件变量将失败并返回EBUSY。 pthread_cond_broadcast 函数以广播的方式唤醒所有等待目标条件变量的线程。 pthread_ cond_signal函数用于唤醒一个等待目标条件变量的线程。至于哪个线程将被唤醒则取决于线程的优先级和调度策略。有时候我们可能想唤醒一个指定的线程但pthread没有对该需求提供解决方法。不过我们可以间接地实现该需求定义一个能够唯一表示目标线程的全局变量在唤醒等待条件变量的线程前先设置该变量为目标线程然后采用广播方式唤醒所有等待条件变量的线程这些线程被唤醒后都检查该变量以判断被唤醒的是否是自己如果是 就开始执行后续代码如果不是则返回继续等待。 pthread_cond_wait函数用于等待目标条件变量。mutex参数是用于保护条件变量的互斥锁以确保pthread_cond_wait操作的原子性。在调用pthread_cond_wait 前必须确保互斥锁mutex已经加锁否则将导致不可预期的结果。pthread_cond_wait 函数执行时首先把调用线程放入条件变量的等待队列中然后将互斥锁mutex解锁。可见从pthread_cond_wait 开始执行到其调用线程被放入条件变量的等待队列之间的这段时间内pthread_cond_signal 和pthread_cond_broadcast等函数不会修改条件变量。换言之,pthread_cond_wait函数不会错 过目标条件变量的任何变化。当pthread_cond_wait函数成功返回时互斥锁mutex将再 次被锁上。 上面这些函数成功时返回0失败则返回错误码。 14.7 线程同步机制包装类 为了充分复用代码同时由于后文的需要我们将前面讨论的3种线程同步机制分别封 装成3个类实现在locker.h文件中如代码清单14-2所示。 14-2locker.h #ifndef LOCKER_H #define LOCKER_H#include exception #include pthread.h #include semaphore.h// 封装信号量的类 class sem { public:sem(){// 初始化信号量初始值为0if( sem_init( m_sem, 0, 0 ) ! 0 ){throw std::exception();}}~sem(){// 销毁信号量sem_destroy( m_sem );}// 等待信号量bool wait(){return sem_wait( m_sem ) 0;}// 发送信号量bool post(){return sem_post( m_sem ) 0;}private:sem_t m_sem; // 信号量 };// 封装互斥锁的类 class locker { public:locker(){// 初始化互斥锁if( pthread_mutex_init( m_mutex, NULL ) ! 0 ){throw std::exception();}}~locker(){// 销毁互斥锁pthread_mutex_destroy( m_mutex );}// 加锁bool lock(){return pthread_mutex_lock( m_mutex ) 0;}// 解锁bool unlock(){return pthread_mutex_unlock( m_mutex ) 0;}private:pthread_mutex_t m_mutex; // 互斥锁 };// 封装条件变量的类 class cond { public:cond(){// 初始化互斥锁if( pthread_mutex_init( m_mutex, NULL ) ! 0 ){throw std::exception();}// 初始化条件变量if ( pthread_cond_init( m_cond, NULL ) ! 0 ){// 初始化失败销毁互斥锁pthread_mutex_destroy( m_mutex );throw std::exception();}}~cond(){// 销毁互斥锁和条件变量pthread_mutex_destroy( m_mutex );pthread_cond_destroy( m_cond );}// 等待条件变量bool wait(){int ret 0;pthread_mutex_lock( m_mutex );ret pthread_cond_wait( m_cond, m_mutex );pthread_mutex_unlock( m_mutex );return ret 0;}// 唤醒等待条件变量的线程bool signal(){return pthread_cond_signal( m_cond ) 0;}private:pthread_mutex_t m_mutex; // 互斥锁pthread_cond_t m_cond; // 条件变量 };#endif这段代码定义了一个简单的多线程同步工具库包括信号量sem 类、互斥锁locker 类和条件变量cond 类。这些类封装了对应的 POSIX 线程库中的原语提供了更方便的接口供多线程编程使用。 具体每个类的作用如下 sem 类 封装了信号量的操作提供了等待信号量和发送信号量的方法。 locker 类 封装了互斥锁的操作提供了加锁和解锁的方法。 cond 类 封装了条件变量的操作提供了等待条件变量和唤醒等待线程的方法。 这样的封装使得多线程编程更加方便和安全避免了直接使用底层原语可能带来的错误。在构造和析构函数中使用了异常处理机制确保在初始化失败时能够抛出异常避免使用不完整或未初始化的对象。 14.8 多线程环境 14.8.1可重入函数 如果一个函数能被多个线程同时调用且不发生竞态条件则我们称它是线程安全的 thread safe)或者说它是可重入函数。Linux库函数只有一小部分是不可重入的比如5.1.4小节讨论的inet_ntoa 函数以及5.12.2小节讨论的getservbyname和getservbyport函数。关于 Linux上不可重入的库函数的完整列表请读者参考相关书籍这里不再赘述。这些库函数之所以不可重入主要是因为其内部使用了静态变量。不过Linux对很多不可重入的库函数 提供了对应的可重入版本这些可重入版本的函数名是在原函数名尾部加上r。比如函数localtime对应的可重入函数是localtime _r。在多线程程序中调用库函数一定要使用其可重入版本否则可能导致预想不到的结果。 14.8.2 线程和进程 思考这样一个问题如果一个多线程程序的某个线程调用了fork函数那么新创建的子进程是否将自动创建和父进程相同数量的线程呢答案是“否”正如我们期望的那样。子进程只拥有一个执行线程它是调用fork的那个线程的完整复制。并且子进程将自动继承父进程中互斥锁(条件变量与之类似的状态。也就是说父进程中已经被加锁的互斥锁在子进程中也是被锁住的。这就引起了一个问题子进程可能不清楚从父进程继承而来的互斥锁的具体状态是加锁状态还是解锁状态。这个互斥锁可能被加锁了但并不是由调用fork 函数的那个线程锁住的而是由其他线程锁住的。如果是这种情况则子进程若再次对该互斥锁执行加锁操作就会导致死锁如代码清单14-3所示。 14-3thread_atfork.c #include pthread.h #include unistd.h #include stdio.h #include stdlib.h #include wait.hpthread_mutex_t mutex;// 子线程函数 void* another( void* arg ) {printf( in child thread, lock the mutex\n );pthread_mutex_lock( mutex );sleep( 5 );pthread_mutex_unlock( mutex ); }// 准备fork的函数 void prepare() {pthread_mutex_lock( mutex ); }// fork完成后的函数 void infork() {pthread_mutex_unlock( mutex ); }int main() {// 初始化互斥锁pthread_mutex_init( mutex, NULL );pthread_t id;// 创建子线程pthread_create( id, NULL, another, NULL );// 在主线程中休眠1秒留时间给子线程获取互斥锁sleep( 1 );// 创建子进程int pid fork();if( pid 0 ){// fork失败等待子线程结束销毁互斥锁然后返回1pthread_join( id, NULL );pthread_mutex_destroy( mutex );return 1;}else if( pid 0 ){// 子进程打印信息尝试获取互斥锁然后退出printf( I am in the child, want to get the lock\n );pthread_mutex_lock( mutex );printf( I cannot run to here, oops...\n );pthread_mutex_unlock( mutex );exit( 0 );}else{// 父进程解锁互斥锁等待子进程结束pthread_mutex_unlock( mutex );wait( NULL );}// 主线程等待子线程结束销毁互斥锁然后返回0pthread_join( id, NULL );pthread_mutex_destroy( mutex );return 0; }这段代码演示了在主线程中创建子线程然后使用 fork 创建子进程。在主线程、子线程、和子进程之间共享一个互斥锁。通过使用 pthread_atfork 函数对 fork 进行了预处理prepare 函数和 fork 后处理infork 函数。 具体的执行流程如下 主线程初始化互斥锁 mutex。创建子线程 another子线程在获取互斥锁后休眠5秒再释放互斥锁。主线程休眠1秒留时间给子线程获取互斥锁。主线程调用 fork 创建子进程。在子进程中尝试获取互斥锁但由于主线程持有互斥锁子进程被阻塞不会输出 “I can not run to here, oop…”。在主线程中解锁互斥锁等待子进程结束。子线程在获取互斥锁后休眠5秒然后释放互斥锁。主线程等待子线程结束。主线程销毁互斥锁。返回0程序结束。 该代码用于演示在 fork 之前如果有线程持有互斥锁可能会导致子进程无法获取互斥锁的情况。在实际编程中需要小心处理多线程和多进程共享资源的情况以避免竞争和死锁等问题。 不过pthread提供了一个专门的函数pthread_atfork,以确保fork调用后父进程和子进 程都拥有一个清楚的锁状态。该函数的定义如下 #include pthread.h int pthread_atfork( void (*prepare)(void), void (*parent)(void), void (*child)(void) );该函数将建立3个fork句柄来帮助我们清理互斥锁的状态。 prepare句柄将在fork调用创建出子进程之前被执行。它可以用来锁住所有父进程中的互斥锁。 parent句柄则是fork 调用创建出子进程之后而fork返回之前在父进程中被执行。它的作用是释放所有在 prepare句柄中被锁住的互斥锁。child句柄是fork返回之前在子进程中被执行。 和parent 句柄一样child 句柄也是用于释放所有在prepare句柄中被锁住的互斥锁。该函数成功时返回0失败则返回错误码。因此如果要让代码清单14-3正常工作就应该在其中的fork调用前加入代码清单14-4 所示的代码。 14.8.3 线程和信号 每个线程都可以独立地设置信号掩码。我们在10.3.2小节讨论过设置进程信号掩码的函数sigprocmask,但在多线程环境下我们应该使用如下所示的pthread版本的sigprocmask函 数来设置线程信号掩码 #include pthread,h #include signal.h int pthread_sigmask ( int how, const sigset_t* newmask, sigset_t* oldmask ); 该函数的参数的含义与sigprocmask的参数完全相同因此不再赘述。pthread_sigmask成功时返回0失败则返回错误码。 由于进程中的所有线程共享该进程的信号所以线程库将根据线程掩码决定把信号发送给哪个具体的线程。因此如果我们在每个子线程中都单独设置信号掩码就很容易导致逻辑错误。此外所有线程共享信号处理函数。也就是说当我们在一个线程中设置了某个信号的信号处理函数后它将覆盖其他线程为同一个信号设置的信号处理函数。这两点都说明我们应该定义一个专门的线程来处理所有的信号。这可以通过如下两个步骤来实现 1)在主线程创建出其他子线程之前就调用pthread_sigmask来设置好信号掩码所有新 创建的子线程都将自动继承这个信号掩码。这样做之后实际上所有线程都不会响应被屏蔽 的信号了。 2)在某个线程中调用如下函数来等待信号并处理之 #include signal.h int sigwait( const sigset_t* set, int* sig );set参数指定需要等待的信号的集合。我们可以简单地将其指定为在第1步中创建的信号掩码表示在该线程中等待所有被屏蔽的信号。参数sig指向的整数用于存储该函数返回 的信号值。sigwait 成功时返回0失败则返回错误码。一旦sigwait 正确返回我们就可以对接收到的信号做处理了。很显然如果我们使用了sigwait就不应该再为信号设置信号处理函数了。这是因为当程序接收到信号时二者中只能有一个起作用。 代码清单14-5取自pthread_sigmask 函数的man手册。它展示了如何通过上述两个步骤 实现在一个线程中统一处理所有信号。 14-5sigmask.c #include pthread.h #include stdio.h #include stdlib.h #include unistd.h #include signal.h #include errno.h/* 简单的错误处理函数宏 */#define handle_error_en(en, msg) \do { errno en; perror(msg); exit(EXIT_FAILURE); } while (0)static void * sig_thread(void *arg) {// 输出线程IDprintf(yyyyy, thread id is: %ld\n, pthread_self());sigset_t aset;int s, sig;sigemptyset(aset);sigaddset(aset, SIGQUIT);sigaddset(aset, SIGUSR1);//s pthread_sigmask(SIG_BLOCK, aset, NULL);sigset_t *set (sigset_t *)arg;for (;;) {s sigwait(set, sig);if (s ! 0)handle_error_en(s, sigwait);printf(Signal handling thread got signal %d\n, sig);} }static void handler(int arg) {// 输出线程IDprintf(xxxxx, thread id is: %ld\n, pthread_self()); }int main(int argc, char *argv[]) {pthread_t thread;sigset_t set;int s;/* 阻塞 SIGINTmain() 创建的其他线程将继承信号掩码的副本。*/// 注册信号处理函数signal(SIGQUIT, handler);// 创建一个子线程s pthread_create(thread, NULL, sig_thread, (void *)set);sigemptyset(set);sigaddset(set, SIGQUIT);sigaddset(set, SIGUSR1);//s pthread_sigmask(SIG_BLOCK, set, NULL);if (s ! 0)handle_error_en(s, pthread_create);printf(sub thread with id: %ld\n, thread);/* 主线程继续创建其他线程和/或执行其他工作 */pause(); /* 虚拟的暂停以便测试程序 */ }这段代码演示了如何在主线程中创建一个子线程子线程用于捕捉信号。主线程阻塞了 SIGQUIT 信号并在子线程中使用 sigwait 函数等待捕捉到信号。 主要功能包括 sig_thread 函数是子线程的入口函数该线程等待捕捉到 SIGQUIT 或 SIGUSR1 信号并在捕捉到信号后输出相关信息。 handler 函数是用于处理 SIGQUIT 信号的信号处理函数。 main 函数初始化一个子线程阻塞 SIGQUIT 信号然后创建子线程。主线程继续执行最后通过 pause 函数进行虚拟暂停以等待信号的触发。在实际情况中可能需要使用其他手段来保证程序的运行。 多线程编程注意事项 在Linux下进行多线程编程时有一些常见的注意事项和最佳实践确保程序的正确性、性能和可维护性。以下是一些需要注意的地方 线程安全性 确保共享数据的访问是线程安全的。使用互斥锁、条件变量等同步原语来保护共享资源避免数据竞争和不一致性。 互斥锁和死锁 避免死锁情况。当多个线程相互等待对方持有的锁时可能发生死锁。确保线程获取锁的顺序是一致的或者使用死锁避免策略。 资源泄漏 注意释放资源特别是在多线程环境中。确保在不再需要的时候正确释放互斥锁、条件变量等资源。 避免全局变量 全局变量在多线程环境中可能导致竞争条件。尽量避免使用全局变量或者使用互斥锁进行保护。 栈大小 确保线程的栈大小足够以防止栈溢出。可以使用线程属性设置栈大小。 线程取消 谨慎使用线程取消功能。在取消线程时可能会导致资源泄漏最好通过其他手段进行线程的退出。 线程局部存储TLS 使用线程局部存储时要注意确保每个线程都有独立的存储空间避免数据共享问题。 信号处理 谨慎在多线程程序中使用信号。某些信号处理机制可能与多线程程序的正常执行发生冲突。 线程创建和销毁开销 避免过度频繁地创建和销毁线程这可能会导致性能问题。使用线程池等机制来管理线程的生命周期。 异常处理 多线程程序中的异常处理需要特别小心。确保异常处理代码是线程安全的并不会引入新的问题。 调试工具 使用适当的调试工具来检测潜在的线程问题如死锁、数据竞争等。Valgrind、GDB等是常用的调试工具。 实时性要求 如果程序对实时性有要求需要使用适当的实时调度策略并避免长时间占用 CPU。 处理器亲和性 可以考虑设置线程与处理器的亲和性以提高性能。 避免忙等待 避免在等待条件时使用忙等待而是使用适当的同步机制如条件变量。 调用库函数的线程安全性 注意调用库函数的线程安全性。一些库函数可能是线程不安全的需要额外的同步措施。 总的来说多线程编程需要仔细考虑并发控制、同步机制、资源管理等方面的问题。合理设计和谨慎操作可以提高多线程程序的稳定性和性能。 后记 截至2024年1月24日18点58分学习完多线程编程这一章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/82919.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

做标书有什么好的网站吗自学学网页设计

过犹不及——《论语先进》 大学考试时,有些老师允许带备cheet sheet(忘纸条),上面记着关键公式和定义,帮助我们快速作答提高分数。传统的检索增强生成(RAG)方法也类似,试图找出精准的知识片段来辅助大语言模型(LLM)。 但这种方法其实有问题…

做网站的多少钱seo排名优化

lsof(List Open Files) 用于查看你进程开打的文件,打开文件的进程,进程打开的端口(TCP、UDP),找回/恢复删除的文件。是十分方便的系统监视工具,因为lsof命令需要访问核心内存和各种文件,所以需要…

三星网站建设内容做手机网站用什么程序好

在机器人的控制中&#xff0c;坐标系统是非常重要的&#xff0c;在ROS使用tf软件库进行坐标转换。 相关链接&#xff1a;http://www.ros.org/wiki/tf/Tutorials#Learning_tf 一、tf简介 我们通过一个小小的实例来介绍tf的作用。 1、安装turtle包 <span>$ rosdep instal…

漳州建设银行网站seo人才招聘

文章目录 什么是AIGC技术&#xff1f;为何AIGC技术如此火热&#xff1f;1. 提高效率与创造力的完美结合2. 拓展应用领域&#xff0c;创造商业价值3. 推动技术创新和发展 AIGC技术案例解析1. 艺术创作&#xff1a;生成独特的艺术作品2. 内容创作&#xff1a;实时生成各类内容3. …

网站建设 500强建站网站建设费属于业务宣传费吗

团队绩效考核 基于各种客观问题本次绩效考核采用和第一次冲刺不一样的标准&#xff0c;根据团队贡献事实打分如下 组员打分&#xff1a; 郭良 &#xff08;9.0&#xff09; 赵承龙 &#xff08;5.5&#xff09; &#xff08;根据组内之前定下的打分细则和本期冲刺过程的事实…

电商食品网站建设江都建设网站

图2-12所示是电源滤波电路中的高频滤波电路。电路中&#xff0c;一个容量很大的电解电容C1(2200F)与一个容量很小的电容C2(0.01F)并联&#xff0c;C2是高频滤波电容&#xff0c;用来进行高频成分的滤波&#xff0c;这种一大一小两个电容相并联的电路在电源电路中十分常见。1.高…

婚庆网站设计自己做网站可以用私有云吗

一、字符设备驱动结构 1. cdev结构体 在Linux内核中&#xff0c;使用cdev结构体来描述一个字符设备 struct cdev {struct kobject kobj; //内嵌kobject对象struct module *owner; //所属的模块const struct file_operations *ops; //该设备的文件操作结构体struct list_head…

交互式网站是什么意思淘宝网站建设评价表

1&#xff09;Open-Resume 介绍 GitHub&#xff1a; https://github.com/xitanggg/open-resume Open-Resume 是一款功能强大的开源 简历生成器 和 简历解析器 。可以帮助我们快速的生成个人简历&#xff0c;并定制化不同的主题和布局风格。该项目的目标是为每个人提供免费的现…

建设银行在上海的招聘网站海洋网络

在Flowable 6.8.0中&#xff0c;以下是每个表的作用并列出每张表的所有字段及其含义&#xff1a; act_evt_log (用于记录流程引擎事件的日志) log_nr&#xff1a;日志编号type&#xff1a;事件类型proc_def_id&#xff1a;流程定义IDproc_inst_id&#xff1a;流程实例IDexecuti…

百度推广做网站吗网络运营商怎么联系

摘要: 拉马努金Q函数在算法分析中的应用&#xff0c;初步体验 【对算法&#xff0c;数学&#xff0c;计算机感兴趣的同学&#xff0c;欢迎关注我哈&#xff0c;阅读更多原创文章】 我的网站&#xff1a;潮汐朝夕的生活实验室 我的公众号&#xff1a;算法题刷刷 我的知乎&#x…

成都网站建设常见问题合肥昱天建设有限公司网站

BulkLoader提供简单的载入函数&#xff0c;不管要载入的是xml、swf还是声音文件&#xff0c;都只使用同一接口。功能强大&#xff0c;十分推荐。 用法&#xff0c;载入xml文件&#xff1a; var bulkLoader:BulkLoader new BulkLoader(main loading);bulkLoader.add(my_xml_fil…

济南建设网站的公司哪家好简单的网站怎样做

Linux是一个强大的操作系统&#xff0c;拥有许多内建的命令。以下是常见的Linux命令及其简单的解释和用法&#xff1a; ls&#xff1a;列出目录内容。 来源&#xff1a;list。用法&#xff1a;ls、ls -l、ls -a cd&#xff1a;改变当前目录。 来源&#xff1a;change director…

小公司怎样自己建网站投资网

中颖51芯片学习5. 类EEPROM操作 一、SH79F9476 Flash存储空间1. 特性2. 分区3. OP_EEPROMSIZE选项设置3. 编程接口4. 代码保护控制模式简介&#xff08;1&#xff09;**代码保护模式0&#xff1a;**&#xff08;2&#xff09;**代码保护模式1&#xff1a;**&#xff08;3&#…

深圳做网站de公司免费网站模版

工作经常使用的SQL整理&#xff0c;实战篇&#xff08;一&#xff09; 原文:工作经常使用的SQL整理&#xff0c;实战篇&#xff08;一&#xff09;工作经常使用的SQL整理&#xff0c;实战篇&#xff0c;地址一览&#xff1a; 工作经常使用的SQL整理&#xff0c;实战篇&#xff…

电子工程网站大全网站开发建设培训

HTML&#xff08;Hypertext Markup Language&#xff09;是一种标记语言&#xff0c;用于描述网页的结构和内容。以下是对网页结构的理解以及网络爬虫在处理不同类型网页时可能遇到的情况&#xff1a; 1. HTML基本结构 HTML文档的基本结构通常包括以下几个部分&#xff1a; …

网站建设基本标准门户网站建设管理

1、Type C 概述 Type-C口有4对TX/RX分线&#xff0c;2对USBD/D-&#xff0c;一对SBU&#xff0c;2个CC&#xff0c;另外还有4个VBUS和4个地线。 当Type-C接口仅用作传输DP信号时&#xff0c;则可利用4对TX/RX&#xff0c;从而实现4Lane传输&#xff0c;这种模式称为DPonly模式…

建筑公司网站页面图片ssh做的大型网站

有时候发现访问磁盘上文件很慢,但是不知道到底是不是硬盘的问题,此时可以使用该工具进行检测以方便排查问题 一、下载 https://github.com/axboe/fio/releases 注:(1)官网地址无法下载(https://bsdio.com/fio/、https://brick.kernel.dk/snaps/) 二、安装 1、Windo…

做直播网站需要多少钱wordpress 娱乐插件

nginx_status_fun (){#函数内容NGINX_PORT$1#端口&#xff0c;函数的第一个参数是脚本的第二个参数&#xff0c;即脚本的第二个参数是段端口号NGINX_COMMAND$2#命令&#xff0c;函数的第二个参数是脚本的第三个参数&#xff0c;即脚本的第三个参数是命令nginx_active(){ #获…

网站布局结构主要分为深圳建设工程信息网站

QUESTION:Dubbo&#xff1a;com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method 问题的解决? ANSWER&#xff1a; 一、原因&#xff1a; 1.需要进行序列化的POJO类没有进行序列化。 多是入参中的一些参数实体类。这个原因是最容易发现的&#xff0c;因为未序列…

电视剧男女直接做视频网站邢台制作网站

问题 D: 优美三角剖分 时间限制: 1 Sec 内存限制: 64 MB提交: 13 解决: 4[提交][状态][讨论版]题目描述 小X同学为了搞好和小C同学的关系&#xff0c;特意寻找了一些优美的图像作为礼物。这是一些由无穷无尽三角形组成的极为优美的图形&#xff0c;小X同学很想实现这些极富美…