引言

        本来,线程在 Windows 中的应用比在 Linux 平台中的应用更广泛。但 Web 服务的发展迫使 UNIX 系列的操作系统开始重视线程。由于 Web 服务器端协议本身具有的特点,经常需要同时向多个客户端提供服务。因此,人们逐渐舍弃进程,转而开始利用更高效的线程实现 Web 服务器端。

一  理解线程的概念

1.1 引入线程的背景

前面的博文中我们介绍了多进程服务器端的实现方法,但很多时候在一个应用程序中使用多个进程,则会存在一些明显的缺点。

  • 创建进程的过程会带来很大的开销

由于 fork 函数是一个开销很大的系统调用,所以创建子进程时会增加一些基本开销,这是因为启动一个新的进程必须分配给其独立的地址空间,建立众多的数据表来维持它的代码段、堆栈段和数据段。

  • 为了完成进程间的数据交换,需要特殊的 IPC(Inter Process Communication,进程间通信)技术

由于每个进程都要自己独立的地址空间,因此必须使用进程间通信的手段,如管道、消息队列、共享内存等。

  • 进程的上下文切换开销很大

进程在内核中的数据结构又称为上下文(Content)。上下文包括3个部分:用户级上下文是进程地址空间的内容;寄存器上下文是进程运行时装入CPU寄存器的内容;系统级上下文是进程在内核中的数据结构。

Linux系统是一个分时操作系统,Linux内核可以同时运行多个进程,并为每一个进程分配CPU时钟周期。当一个进程的CPU时钟周期结束后,Linux内核会调度另一个进程到CPU上执行,如此往复,这就是进程的上下文切换(Context Switching)。

操作系统在对两个进程进行切换时,CPU会收到一个软中断,这时原进程上下文将被保存起来,称之为保护现场,然后CPU执行另一个进程。当原进程再次被调度到CPU上运行时,上下文被还原到相关位置上,称之为还原现场,这就是上下文切换的过程,保存上下文的数据空间称为 u 区,是Linux 内核为进程分配的存储空间。

简而言之,进程的上下文切换就是切换不同进程到CPU上运行的过程,如果内存空间不足时,还需要将被替换的进程相关信息移出内存,临时存放到硬盘上(Swap分区),并读入待运行进程的相关信息到内存中。可以看到,这个上下文切换过程是很费时费力的,即使通过优化加快速度,也会存在一定的局限。

为了在一定程度上克服多进程的上述缺点,人们引入了线程(Thread)。这是为了将进程的各种缺点降至最低限度(不能直接消除)而设计出的一种“轻量级进程”(LightweightProcess,LWP)。线程相比进程具有如下优点:

  • 线程的创建和上下文切换比进程的创建和上下文切换速度更快。
  • 线程间交换数据时无需特殊技术。

1.2 线程与进程的差异

线程是为了解决如下困惑登场的:

嘿!为了得到多条代码执行流而复制整个进程内存空间的负担太重了!

        每个进程的内存空间都由保存变量的 “数据区”、使用malloc等函数动态分配的堆区(Heap)、函数运行时使用的栈区(Stack)构成。每个进程都有这种独立的内存空间,多个进程的内存结构如下图 1 所示。

图1  进程间独立的内存结构

 但如果以获得多个代码执行流为主要目的,则不应该像上图 1 那样完全分离内存结构,而只需分离栈区域。通过这种方式可以获得如下优势。

  • 上下文切换时不需要切换数据区和堆区。
  • 可以利用数据区和堆区交换数据。

实际上这就是线程。线程为了保持多条代码执行流而隔开了栈区域,因此具有如下图 2 所示的内存结构。

图2  线程的内存结构

 如上图 2 所示,多个线程将共享数据区和堆区。为了保持这种结构,线程将在进程内创建并运行。也就是说,进程和线程可以定义如下形式:

  • 进程:在操作系统构成单独执行流的单位。
  • 线程:在进程构成单独执行流的单位。

进程与线程的本质区别:进程是操作系统进行资源分配和调度的基本单位,是程序执行的最小单位;而线程是CPU执行和调度的基本单位。

        如果说进程在操作系统内部生成多个执行流,那么线程就在同一进程内部创建多条执行流。因此,操作系统、进程、线程之间的关系可以通过下图 3 表示。

图3  操作系统、进程、线程之间的关系

 二  Linux 线程的实现

2.1 Linux 线程库

        早起Linux系统不支持线程,直到1996年,Xavier Leroy 等人才开发出第一个基本符合 POSIX 标准的线程库 LinuxThreads。但 LinuxThreads 效率低。自内核 2.6 版本开始,Linux才真正提供内核级的线程支持,并有两个组织致力于编写新的线程库:NGPT(Next Generation POSIX Threads) 和 NPTL(Native POSIX Threads Library)。不过前者在 2003 年就放弃了,因此新的线程库就称为 NPTL。NPTL 比 LinuxThreads 效率高,且更符合 POSIX(Portable Operating System Interface)规范,所以它已经成为 glibc 库的一部分。本文所有线程相关的例程使用的线程库都是 NPTL

知识补充》默认Linux 线程库 — NPTL

        Linux内核从 2.6 版本开始,提供了真正的内核线程,默认使用的线程库是 NPTL,该线程库在可用性、稳定性以及 POSIX 兼容性方面都远远优于最初设计的 LinuxThreads 线程库。用户可以使用如下命令来查看当前Linux系统上所使用的线程库。比如本人使用的Linux系统如下:

$ cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.17

英语缩略词

POSIX(Portable Operating System Interface,可移植操作系统接口) 是为了提高 UNIX 系统操作系统间的移植性而制定的 API 规范。

2.2 用户态线程

        用户态线程是由进程负责调度管理、高度抽象化的、与硬件平台无关的线程机制。其最为显著的标志是,进程在创建多个线程时不需要操作系统内核的参与,也不直接对CPU标志寄存器进行操作。用户态线程的优势在于下面两个方面。

  • 减少多线程的系统开销:进程下的线程进行调度切换时,不需要进行系统调用。同一个进程内可创建的线程数没有限制。
  • 用户态线程实现灵活多变:可根据实际需要设计相应的用户态线程机制,对于实时性要求高的程序格外重要。

        虽然用户态线程有快速和灵活的特性,但是也存在一个严重的问题,如果进程中的其中一个线程被阻塞,则进程会进入睡眠状态,该进程内的其他线程也会被阻塞,例如,当一个线程由于磁盘 I/O 而阻塞时,其他线程同样也不能运行。另外,用户态线程不能发挥多路处理器和多核处理器的优势。

2.3 内核态线程

        内核态线程是由 Linux 操作系统根据 CPU 硬件的特点,以硬件底层模式实现的线程机制。内核态线程由内核来管理的,在每一个CPU时间片内,都是由内核来负责调度进程内的线程。由于内核参与了用户态进程的调度,所以就涉及了内核态与用户态上下文切换。通常所说的内核态线程切换速度慢就是由于这个原因导致的。

        使用内核态线程明显一个好处就是当进程内的某个线程阻塞时,其他线程仍可以利用CPU时间片运行。内核态线程机制将所有线程按照同一调度算法调度,更有利于发挥多路CPU和多核CPU所支持的并发处理特性的优势。

        内核态线程相较于用户态线程,内核态线程的系统开销稍大,并且必须通过系统调用实现,对硬件和 Linux 内核版本的依赖性较高,不利于程序移植。

三  线程的操作

        本节我们将以 pthread 线程为标准讲解 POSIX 线程库的使用方法。pthread 线程对应的函数库为 libpthread。它支持 NPTL 线程模型,以用户态线程机制实现。该函数库的接口被定义在 pthread.h 头文件中。

补充说明》NPTL线程库 与 libpthread 函数库的关系

在NPTL实现中,用户创建的线程和内核中调度实体的关系是 1:1,什么意思呢?就是说当在进程内创建一个线程时,在内核中会创建一个与该线程对应的数据结构实体,称为内核态线程。因此,一个用户空间线程被映射为一个内核空间线程。

libpthread 函数库是使用 NPTL 的方式创建线程的,因此使用该函数库创建的线程,属于内核态线程。

这涉及到线程模型问题,请参考下面的博文链接

Linux线程模型

Linux 线程实现模型

3.1 线程的创建和执行流程

线程具有单独的执行流,因此需要单独定义线程的 main 函数,还需要请求操作系统在单独的执行流中执行该函数,完成该功能的函数如下。

  • pthread_create() — 创建一个线程。
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

参数说明

  • thread:保存新创建线程ID的变量地址值。线程与进程相同,也需要区分不同线程的ID。
  • attr:用于传递线程属性的参数,传递 NULL 时,创建默认线程属性。
  • start_routine:相当于线程main函数的、在单独执行流中执行的函数地址值(函数指针)。
  • arg:通过第三个参数传递调用函数时包含传递参数信息的变量地址值。

返回值】成功时返回0,失败时返回错误码。

  • pthread_t 数据类型说明

#include <bits/pthreadtypes.h>

typedef unsigned long int pthread_t;

可见,pthread_t 是一个无符号长整型类型。实际上,Linux上几乎所有的资源标识符都是一个整型数,比如 socket、各种 System V IPC 标识符等。

编程实例:使用 pthread_create 函数创建线程的示例。

  • thread1.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void* thread_main(void *arg);int main(int argc, char *argv[])
{pthread_t tid;          //用来保存线程IDint thread_param = 5;//创建线程if(pthread_create(&tid, NULL, thread_main, &thread_param) != 0){puts("pthread_create() error!");return -1;}sleep(10);puts("End of main()");return 0;
}//线程执行main函数
void* thread_main(void *arg)
{int i;int cnt = *(int*)arg;for(i=0; i<cnt; i++){sleep(1);puts("running thread");}return NULL;
}
  • 代码说明
  • 第13行:调用 pthread_create 函数创建一个线程,从 thread_main 函数调用开始,在单独的执行流中运行。同时在调用 thread_main 时向其传递 thread_param 变量的地址值。
  • 第18行:调用 sleep 函数使main函数暂停10秒,这是为了延迟进程的终止时间。main函数中执行第20行的 return 语句后终止进程,同时也将终止进程内部创建的线程。因此,为保证线程的正常执行而添加这条语句。
  • 第24、27行:传入 arg 参数的是第13行 pthread_create 函数的第四个参数。
  • 运行结果

$ gcc thread1.c -o thread1 -lpthread
$ ./thread1
running thread
running thread
running thread
running thread
running thread
End of main()

        从上述运行结果中可以看出,线程相关代码在编译时需要添加 -lpthread 选项,作用是把程序与 libpthread 函数库相连,只有这样才能调用在头文件 pthread.h 中声明的函数。上述程序的执行流程如下图 4 所示。

图4  示例thread1.c的执行流程

        上图4的虚线代表执行流程,向下的箭头指的是执行流,横向箭头是函数调用。接下来将 thread1.c 示例的第18行的sleep函数的调用语句改为如下形式:

sleep(2);
  • 运行结果

$ gcc thread1.c -o thread1 -lpthread
$ ./thread1
running thread
running thread
End of main()

从运行结果可以看到,只输出了两次 "running thread" 字符串。这是因为main函数返回后整个进程将被销毁,如下图 5 所示。

图5  终止进程和线程

        正因如此,我们之前在 thread1.c 示例中通过调用 sleep 函数向线程提供了充足的执行时间。但是通过调用 sleep 函数控制线程的执行流程,是不切实际的办法。稍有不慎,就会干扰程序的正常执行流,因为它无法准确预测 thread_main 函数的运行时间,并让 main 函数刚好等待这么长的时间。因此,我们不用 sleep 函数,而是通常利用下面的函数控制线程的执行流。

  • pthread_join() — 等待一个线程的结束。
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);

参数说明

  • thread:线程ID。指的是被等待终止的线程ID。
  • retval:保存第一个参数标识的线程终止时返回值的指针变量地址值。它是一个二级指针。

返回值】成功时返回0,失败时返回错误码。

        调用 pthread_join 函数的进程(或线程)将进入等待状态,直到第一个参数标识的线程终止运行才返回。而且可以通过第二个参数得到线程结束时的返回值信息。

编程实例:pthread_join 函数的使用示例。

  • thread2.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>void* thread_main(void *arg);int main(int argc, char *argv[])
{pthread_t tid;          //用来保存线程IDint thread_param = 5;void *thr_ret;          //声明一个空指针变量//创建线程if(pthread_create(&tid, NULL, thread_main, &thread_param) != 0){puts("pthread_create() error!");return -1;}//等待线程终止运行if(pthread_join(tid, &thr_ret) != 0){puts("pthread_join() error!");return -1;}printf("Thread return message: %s\n", (char*)thr_ret);free(thr_ret);  //释放动态内存return 0;
}//线程执行main函数
void* thread_main(void *arg)
{int i;int cnt = *(int*)arg;char *msg = malloc(sizeof(char) * 50);  //动态内存分配strcpy(msg, "Hello, I`m thread~");for(i=0; i<cnt; i++){sleep(1);puts("running thread");}return (void*)msg;
}
  • 代码说明
  • 第23行:在main函数中,针对第16行创建的线程调用 pthread_join 函数。因此,main 函数将等待线程ID为 tid 的线程终止运行。
  • 第13、23、45行:通过这三条语句获取线程的返回值。简言之,第45行返回的值(这是个地址值)将保存到第23行第二个参数 thr_ret。需要注意的是,该返回值是 thread_main 函数内部动态分配的内存空间的地址值,在第29行语句中,调用 free 函数释放掉动态分配的内存。动态内存分配是在进程地址空间的堆区上开辟的,需要程序员手动释放。
  • 运行结果

$ gcc thread2.c -o thread2 -lpthread
$ ./thread2
running thread
running thread
running thread
running thread
running thread
Thread return message: Hello, I`m thread~

thread2.c 示例的执行流程,如下图 6 所示。

图6  调用pthread_join函数

 3.2 线程的销毁(3种方法)

单个线程可以通过3种方式退出,可以在不终止整个进程的情况下,停止线程的控制流。

(1)线程可以直接从启动例程(也就是线程函数)中返回,即执行return语句,返回值是线程的退出码。

(2)线程函数本身调用 pthread_exit()。函数返回线程退出后传出来的retval指针。

(3)线程可以被同一进程中的其他线程取消。即其他线程调用 pthread_cancel() 函数。

  • pthread_exit() — 线程退出函数。
#include <pthread.h>void pthread_exit(void *retval);

参数说明

  • retval:用来保存线程退出时的终止状态信息。

函数说明】当线程调用 pthread_exit 函数时,线程主动退出,终止运行。

  • pthread_cancel() — 线程取消函数。
#include <pthread.h>int pthread_cancel(pthread_t thread);

参数说明

  • thread:需要被取消的线程的线程ID。

返回值】成功时返回0,失败时返回对应的错误码。

函数说明】线程可以通过调用 pthread_cancel 函数来请求取消同一进程内的其他线程。

        在调用 pthread_cancel 函数取消一个线程后,需要调用相应的函数对线程退出之后的环境进行清理,这些函数被称为线程清理处理程序(Thread Cleanup Handler),线程可以建立多个清理处理程序,对这些函数的标准调用格式说明如下:

#include <pthread.h>void pthread_cleanup_push(void (*routine)(void *), void *arg);void pthread_cleanup_pop(int execute);

        pthread_cleanup_push 函数将子程序 routine 连同它的传入参数 arg 一起压入当前线程的 cleanup 处理程序的堆栈;当当前线程调用 pthread_exit 函数或者是通过 pthread_cancel 函数终止执行时,堆栈中的处理程序将按照压栈的相反顺序依次被调用。

        而 pthread_cleanup_pop 函数从线程的 cleanup 处理程序堆栈中弹出最上面的一个处理程序并执行它。

        需要注意的是,其他真正对线程执行清理工作的是在 pthread_cleanup_push 函数中作为参数传递进去的 routine 函数,其参数通过 arg 传递进去,其在线程执行如下动作的时候被调用:

  • 调用 pthread_exit 函数的时候。
  • 响应取消线程请求时,即调用 pthread_cancel 函数的时候。
  • 用非零execute参数调用 pthread_cleanup_pop 时。

如果 pthread_cleanup_pop 函数的 execute 参数被传入 0 时,清理函数将不会被调用,无论在哪种情况下,pthread_cleanup_pop 都将删除 pthread_cleanup_push 函数调用时建立的线程清理处理程序。

  • pthread_detach() — 分离线程。
#include <pthread.h>int pthread_detach(pthread_t thread);

参数说明

  • thread:需要分离的线程标识符,即线程ID。

返回值】成功时返回0,失败时返回错误编号。

        在Linux系统中,线程一般有分离和非分离两种状态。默认情况下,线程是非分离状态的,父线程维护子线程的某些信息并等待子线程的结束,在没有显式调用 pthread_join 的情形下,子线程结束时,父线程维护的信息可能还没有得到及时释放,如果父线程中大量创建这种非分离状态的子线程(在Linux系统中调用 pthread_create 函数),可能会出现堆栈空间不足的错误,其出错的返回值是 12。而对于分离线程来说,不会有其他的线程等待它的结束,因此线程终止运行后,其所占用的内存空间可以立即释放。

        调用 pthread_detach 函数后,不能再针对相应线程调用 pthread_join 函数,因为对分离线程调用pthread_join会产生未定义的行为。这需要格外注意。

  • 关于线程终止的内容,参考博文链接

线程终止

3.3 可在临界区内调用的函数

        上文的示例中只创建了一个线程,接下来的示例将开始创建多个线程。当然,无论创建多少个线程,其创建方法没有区别。但关于线程的运行需要考虑:“多个线程同时调用函数时(执行时)可能产生问题”。这类函数内部存在临界区(Critical Section),也就是说,多个线程同时执行这部分代码时,可能引发问题。临界区中至少存在一条这类代码。

知识补充什么是临界区?

        在任意时刻只允许一个线程对共享资源进行访问的区域。这个临界区可能是代码块、或是共享内存。

        例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。 如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。为了确保读线程读取到的是经过修改后的值,就必须在向全局变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。

        上面示例中,从代码角度讲,全局变量就是共享资源,访问全局变量的语句或语句块就是临界区;从内存角度讲,全局变量所在内存空间中的数据就是共享资源,而内存空间的大小就是临界区。

        稍后将讨论哪些代码可能成为临界区,多个线程同时执行临界区代码时会产生哪些问题等内容。现阶段只需理解临界区的概念即可。根据临界区是否引起问题,函数可以分为以下两类:

  • 线程安全函数(Thread-safe function)
  • 非线程安全函数(Thread-unsafe function)

        线程安全函数被多个线程同时调用时不会引发问题。反之,非线程安全函数被多个线程同时调用时会引发问题。但这并非关于有无临界区的讨论,线程安全函数中同样可能存在临界区,只是在线程安全函数中,同时被多个线程调用时可通过一些措施避免问题的发生。

        幸运的是,大多数标准函数都是线程安全函数。更幸运的是,我们不用自己区分线程安全的函数和非线程安全函数。因为这些操作系统平台在定义非线程安全函数的同时,提供了具有相同功能的线程安全函数。比如,下面的函数:

#include <netdb.h>
#include <sys/socket.h>//非线程安全函数
struct hostent *gethostbyname(const char *name);//线程安全函数
int gethostbyname_r(const char *name,struct hostent *ret, char *buf, size_t buflen,struct hostent **result, int *h_errnop);

        线程安全函数的名称后缀通常为 _r。既然如此,多个线程同时访问的代码块中应该调用 gethostbyname_r,而不是 gethostbyname?当然!但这种方法会给程序员带来沉重的负担。幸好可以通过如下方法自动将 gethostbyname 函数调用改为 gethostbyname_r 函数调用!

声明头文件前定义 _REENTRANT 宏。

        gethostbyname 函数和 gethostbyname_r 函数的函数名和参数声明都不同,因此,这种宏声明方式拥有巨大的吸引力。另外,无需为了上述宏定义特意添加 #define 宏定义,可以在 gcc 编译源程序时通过添加 -D_REENTRANT 选项定义宏。示例如下:

gcc -D_REENTRANT mythread.c -o mthread -lpthread

下文的示例中编译线程相关代码时,均默认添加 -D_REENTRANT 选项。

3.4 工作(Worker)线程模型

下面我们将介绍常见多个线程的情况。下面给出此类示例。

        将要介绍的示例将计算 1 到 10 的和,但并不是在 main 函数中进行累加运算,而是创建2个线程,其中一个计算 1 到 5 的和,另一个线程计算 6 到 10 的和,main 函数只负责输出运算结果。这种方式的编程模型称为 “工作线程(Worker thread)模型”。计算1到5之和的线程与计算6到10之和的线程将成为main主线程管理的工人(worker)。最后,给出示例代码前先给出程序执行流程图,如下图 7 所示。

图7  示例thread3.c的执行流程
  •  thread3.c
#include <stdio.h>
#include <pthread.h>void* thread_summation(void *arg);int sum = 0;  //声明全局变量int main(int argc, char *argv[])
{pthread_t tid1, tid2;int range1[]={1, 5};int range2[]={6, 10};pthread_create(&tid1, NULL, thread_summation, range1);pthread_create(&tid2, NULL, thread_summation, range2);pthread_join(tid1, NULL);pthread_join(tid2, NULL);printf("result: %d\n", sum);return 0;
}void* thread_summation(void *arg)
{int start = ((int*)arg)[0];int end = ((int*)arg)[1];while(start <= end){sum += start;start++;}return NULL;
}

        在 thread3.c 示例中,我们可以注意到:“两个线程可以直接访问全局变量sum。” 这是因为全局变量是存放在进程地址空间的数据区,是进程内的所有线程可以共享的内存区域。

  • 运行结果

$ gcc thread3.c -D_REENTRANT -o thread3 -lpthread
$ ./thread3
result: 55

        运行结果为55,虽然正确,但示例本身还是存在问题的。此处存在临界区相关问题,因此再介绍另一示例。该示例与 thread3.c 示例相似,只是增加了发生临界区相关错误的可能性,即使在高配置系统环境下也容易验证产生的错误。

  • thread4.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>#define THREAD_NUM 100void* thread_inc(void *arg);
void* thread_des(void *arg);
long long num = 0;  //long long 数据类型是8字节整型int main(int argc, char *argv[])
{pthread_t tid[THREAD_NUM];int i;printf("sizeof(long long): %d(bytes)\n", sizeof(long long));  //查看long long的大小for(i=0; i<THREAD_NUM; i++){if(i % 2 != 0)pthread_create(&tid[i], NULL, thread_inc, NULL);elsepthread_create(&tid[i], NULL, thread_des, NULL);}for(i=0; i<THREAD_NUM; i++)pthread_join(tid[i], NULL);printf("result: %lld\n", num);return 0;
}void* thread_inc(void *arg)
{int i;for(i=0; i<50000000; i++)num += 1;return NULL;
}void* thread_des(void *arg)
{int i;for(i=0; i<50000000; i++)num -= 1;return NULL;
}

        示例 thread4.c 中共创建了100个线程,其中一半线程执行 thread_inc 函数中的代码,对全局变量 num 执行自增加1操作;另一半线程执行 thread_des 函数中的代码,对全局变量 num 执行自减减1操作。因此,全局变量 num 经过增减过程后,最后输出结果应为 0,通过运行结果观察是否真能得到我们期望的结果呢?

  • 运行结果

$ gcc thread4.c -D_REENTRANT -o thread4 -lpthread
$ ./thread4
sizeof(long long): 8(bytes)
result: 19774230
$ ./thread4
sizeof(long long): 8(bytes)
result: 39285629
$ ./thread4
sizeof(long long): 8(bytes)
result: 38318569

        从上面的运行结果可以看出,运行结果并不是 0。而且每次运行的结果均不同。虽然其原因尚不得而知,但可以肯定的是,这对于多线程的应用是个大问题。

四  线程存在的问题和临界区

4.1 多个线程访问同一共享变量存在的问题

        上文中的 thread4.c 示例中存在如下问题:

两个线程同时访问全局变量 num。

        此处的 “访问” 是指值的更改操作。虽然示例中访问的对象是全局变量,但这并非全局变量引发的问题。任何内存空间——只要被同时访问——都有可能发生问题。

不是说线程会分时使用CPU吗?那应该不会出现同时访问变量的情况啊。

        当然,此处的 “同时访问” 与我们所想的有一定区别。下面通过示例解释 “同时访问” 的含义,并说明为何会引起问题。假设两个线程要执行将变量 num 逐次加 1 的操作,如下图 8 所示。

图8  等待中的两个线程

        上图 8 中描述的是两个线程准备将变量 num 的值加 1 的情况。在此状态下,线程1将变量num的值增加到100,线程2再访问num时,变量num的值将按照我们的预想保存101。可事实果真如此吗?

需要说明的是,变量num是存储在内存中的,而线程是在CPU上运行的,对变量num执行加1操作可以分为如下三个步骤进行:

  • 取操作数num到CPU寄存器中。
  • 对寄存器中的操作数加1。
  • 将寄存器中的值写回到存放num变量的内存单元中。

以上三个步骤完成,才算执行完了一次  num += 1; 语句。这涉及到计算机组成原理相关的内容,可以查阅相关资料了解详情。

下图 9 是线程1将变量num加1之后的情形。

图9  线程的加法运算1-1

        图 9 中需要注意值的增加方式,值的增加需要CPU运算完成,变量num中的值不会自动增加。线程1首先需要读该变量的值并将其传递给CPU,获得加1之后的结果100,最后再把结果写回变量num,这样变量num的值就变成100。接下来给出线程2的执行过程,如下图 10 所示。

图10  线程的加法运算1-2

        线程2先从内存中取出变量num的值100,执行加1运算后将101写回给变量num。但这是最理想的情况。线程1完成增加num之前,线程2完全有可能通过线程切换得到CPU使用权。

        下面从头再来,下图 11 描绘的是线程1读取变量num的值并完成加1运算时的情况,即完成了 num += 1; 语句 的前两步,只是第三步加1后的结果尚未写回到变量num。

图11  线程的加法运算2-1

        接下来线程1就要将100写回到变量num中,但在执行这第三步操作前,执行流程跳转到了线程2,线程2从内存获取到变量num的值99,并完成了加1运算,并将加1之后的结果写入变量num,此时num的值变为100。如下图 12 所示。

图12  线程的加法运算2-2

        从上图 12 中可以看到,变量num的值尚未被线程1加到100,因此线程2读到的变量num的值仍为99,结果是线程2将变量num的值修改成100。还剩下线程1将运算后的值写回变量num的操作。接下来给出该过程,如下图 13 所示。

图13  线程的加法运算2-3

        此时,线程1将自己的运算结果100再次写入变量num,结果变量num变成100。可以看到,虽然线程1和线程2各做了一次加1运算,却得到了意想不到的结果。因此,当其中一个线程访问变量num时,应该阻止其他线程的访问,直到该线程完成运算为止。这就是同步(Synchronization)。从这个示例中我们可以意识到多线程编程中 “同步” 的必要性,也就能理解 thread4.c 示例中的运行结果了。

4.2 临界区位置

在 thread4.c 示例中我们可以发现:“同时运行多个线程时引起问题的是多条语句构成的代码块。

全局变量 num 是否应该视为临界区?不是!因为它不是引起问题的原因。该全局变量并非同时运行,只是代表内存区域的声明而已。临界区通常位于由线程运行的函数内部。下面观察示例 thread4.c 中的的两个线程函数。

void* thread_inc(void *arg)
{int i;for(i=0; i<50000000; i++)num += 1;  //临界区return NULL;
}void* thread_des(void *arg)
{int i;for(i=0; i<50000000; i++)num -= 1;  //临界区return NULL;
}

        由上面的代码注释可知,临界区并非全局变量num本身,而是访问num的2条语句。这两条语句可能由多个线程同时运行,这是引发问题的直接原因。产生的问题可以整理为如下三种情况:

  • 两个线程同时执行 thread_inc 函数。
  • 两个线程同时执行 thread_des 函数。
  • 两个线程分别执行 thread_inc 和 thread_des 函数。

参考

《TCP-IP网络编程(尹圣雨)》第18章 - 多线程服务器端的实现

《Linux高性能服务器编程》第14章 - 多线程编程

《Linux典藏大系:Linux环境C程序设计(第2版)》第17章 - 线程控制

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

相关文章

  1. 2022.01.20_Java学习总结_散列表、泛型

    1. Set 1.1 HashSet使用 HashSet set new HashSet();set.add(1);set.add("asd");set.remove("asd");System.out.println(set.size());set.isEmpty();for (Object object : set) {}2. 散列表 2.1 概述 * 散列表结构 可以理解为 数组中保存的是链表的首节…...

    2024/4/18 21:00:27
  2. 前端开发 安卓ios兼容问题集合(持续更新~)

    小程序clip方法(官方未修复) 在小程序中使用canvas画圆角图形(比如 带圆角的背景图 或者 圆形头像)都需要使用clip方法 当连续 使用次clip方法时,安卓机型和模拟器显示正常,ios会出现只有第一个生效 小程序wx.previewImage的bug(官方未修复) a&#xff09;:正常情况为页面进…...

    2024/5/6 10:00:20
  3. 江苏大学 操作系统 复习

    基于江苏大学教材《操作系统设计原理&#xff08;第二版&#xff09;》&#xff0c;科学出版社出版。詹永照、薛安荣主编。 第一章 绪论 1.操作系统定义&#xff1a; 是计算机系统的一种系统软件&#xff0c;用于管理计算机的资源和控制程序的执行。 2.提供用户的使用方式&…...

    2024/5/6 14:12:06
  4. Canvans (一)

    参考&#xff1a;使用canvas来绘制图形 - Web API 接口参考 | MDN 介绍 <canvas>元素用来绘制2D图形&#xff08;可以通过改变位置实现伪3D效果&#xff0c;WebGL 使用了基于OpenGL ES的3D上下文&#xff09;。 Canvas 的默认大小为300像素150像素&#xff08;宽高&am…...

    2024/5/6 4:40:59
  5. 对比阿里云服务器和腾讯云服务器两者的不同之处

    本人两家都有接触&#xff0c;也推荐过朋友用过两家的服务器及其他云资源&#xff0c;今天从市场、性能、服务、价格等几个方面简单聊聊 腾讯云代金券礼包腾讯云热门活动机型秒杀地址阿里云代金券领取以及热门产品秒杀 阿里云和腾讯云是国内数一数二的云计算平台&#xff0c;也…...

    2024/4/17 15:42:54
  6. MySQL进阶篇之事务的隔离级别详解

    文章目录前言一、什么是事务隔离级别&#xff1f;二、四大隔离级别详解1.读未提交&#xff08;Read Uncommitted&#xff09;2.读已提交&#xff08;Read Committed&#xff09;3.可重复读&#xff08;Repeatable Read&#xff09;4.串行化&#xff08;Serializable&#xff09…...

    2024/4/14 11:44:35
  7. JS大文件上传解决方案

    第一点&#xff1a;Java代码实现文件上传 FormFile file manform.getFile(); String newfileName null; String newpathname null; String fileAddre "/numUp"; try { InputStream stream file.getInputStream();// 把文件读入 String filePath request.…...

    2024/4/14 11:44:25
  8. vue脚手架(vue-cli)快速搭建项目教程(最新版)

    亲爱的小伙伴们,我们又见面了,看到你们就是我创作下去的勇气,我是小招一个不折不扣又有点闷骚的程序员,在学习vue的路上我也遇到很多坑,吃过很多亏,流过很多泪,今天就从自己是小白时候的第一视角给大家分享点点滴滴,希望能帮助到更多想入门vue的小伙伴,从此有房有车,…...

    2024/4/15 6:28:57
  9. Python源码剖析(五)列表对象

    bilibili视频讲解&#xff1a;https://space.bilibili.com/431392724 b站用户名&#xff1a;平凡的久月 1. PyListObject 变长对象&#xff08;数据长度在定义时是不知道的&#xff0c;只能在创建时才能确定&#xff09; 与字符串对象不同的是支持插入删除操作&#xff0c;运…...

    2024/4/14 11:45:15
  10. Go 与 C 语言的互操作

    这里写目录标题1. Go 与 C 语言的互操作1.1. Go 调用 C 代码的原理1.2. 在 Go 中使用 C 语言的类型1.2.1. 原生类型1.2.1.1. 数值类型1.2.1.2. 指针类型1.2.1.3. 字符串类型1.2.1.4. 数组类型1.2.1.5. 自定义类型1.2.1.5.1. 枚举(enum)1.2.1.5.2. 结构体(struct)1.2.1.5.3. 联合…...

    2024/5/6 14:25:57
  11. 封装作用实现含义

    我要看电视&#xff0c;只需要按一下开关和换台就可以了。有必要了解电视机内部的结构吗?有必要碰碰显像管吗?制造厂家为了方便我们使用电视&#xff0c;把复杂的内部细节全部封装起来&#xff0c;只给我们暴露简单的接口&#xff0c;比如&#xff1a;电源开关。具体内部是怎…...

    2024/4/18 17:31:26
  12. Taro 正式发布 3.4 版本: 全面支持 Preact Vue 3.2

    距 Taro v3.4 beta 版本的发布已有一段时间&#xff0c;期间我们完善了对 Preact 和 Vue3 的支持&#xff0c;加入了一些有趣的特性&#xff0c;更是对 H5 作了大幅度的优化与调整&#xff0c;并于近期发布了 v3.4 的正式版本。 上月我们还推出了支持开发鸿蒙应用的 v3.5.0 can…...

    2024/4/14 11:45:45
  13. Redis订阅系统

    Redis发布订阅 订阅/发布消息图&#xff1a;...

    2024/4/14 11:45:25
  14. Vulnhub-shenron-1:神龙1靶机渗透攻略

    下载地址&#xff1a;http://www.vulnhub.com/entry/shenron-1,630/ 虚拟化环境virtualbox 目标&#xff1a;获取两个flag ​ 信息收集 nmap扫描 nmap -sC -sV 192.168.210.181 发现80http &#xff0c;22ssh 网站信息收集 可以看到是一个apache的默认页 ​ 扫描一下后…...

    2024/4/7 5:34:26
  15. 用户规模增长几近停滞,2022年应用下载增长需更多渠道

    当互联网人口红利衰减&#xff0c;对用户的争夺&#xff0c;让各应用市场竞争加速&#xff0c;应用公司的「内卷」、研发成本的飙升&#xff0c;都是为了在当下的存量环境中抢得一席之地。 有大佬用VUCA一词形容当下的时代特性&#xff0c;即Volatility(易变性)、Uncertainty(…...

    2024/4/14 11:45:50
  16. JMeter的基本使用

    目录 一、下载JMeter 二、JMeter界面设置 三、新建线程组(Thread Group) 四、新建HTTP请求(HTTP Request) 五、添加信息头管理器(HTTP Header Manager) ​六、创建结果视图(Views Results Tree) 七、运行查看结果 一、下载JMeter 下载地址&#xff1a;Apache JMeter - …...

    2024/4/18 0:45:53
  17. 特殊excel表格导出模板

    1、代码 package com.li.zzh.fileUtils;import java.io.BufferedOutputStream; import java.io.IOException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;import javax.servlet.http.H…...

    2024/4/17 2:31:23
  18. 部署web项目在腾讯云当中

    一、首先需要去腾讯云 购买一个云服务器&#xff0c;个人可以免费领取一个十五天的体验版. https://cloud.tencent.com/act/free 二、在自己电脑点击开始菜单-> Run&#xff0c;输入"mstsc"命令&#xff0c;即可打开远程桌面连接对话框。 在输入框中输入腾讯云服…...

    2024/4/14 11:45:35
  19. 4.1跳表

    零 跳表介绍 emsp;emsp;emsp;emsp;跳表底层是一个链表。这个链表是按大小顺序排列的。 emsp;emsp;emsp;emsp;然后在底层数据链表上建N层索引&#xff0c;如图所示&#xff1a; 0------------->5 | | 0------------->5 …...

    2024/4/19 9:08:55
  20. C盘满了怎么解决

    总是无故爆满涨红的C盘可谓是电脑“神秘”现象之一&#xff0c;明明只是办公、学习专用却有一堆看不懂的文件侵占C盘空间。C盘状态不佳将严重影响电脑系统的运行&#xff0c;伴随一系列卡顿闪退让人瞬间emo&#xff0c;又不想重装更不想重买&#xff0c;驱动人生教你如何轻松解…...

    2024/4/14 11:45:40

最新文章

  1. 【PyTorch】7-生态简介

    PyTorch&#xff1a;7-生态简介 注&#xff1a;所有资料来源且归属于thorough-pytorch(https://datawhalechina.github.io/thorough-pytorch/)&#xff0c;下文仅为学习记录 7.1&#xff1a;torchvision 7.1.1&#xff1a;简介 The torchvision package consists of popula…...

    2024/5/6 16:49:35
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/6 9:38:23
  3. 自定义OPPO-r9s的kernel内核,并开启安卓支持docker

    0. 版本说明 本文提供了OPPO手机r9s的内核编译方法&#xff0c;并开机支持docker。用的是开源lineage14.1的rom。 我这边基于开源lineage14.1&#xff0c;打了一个docker内核编译镜像(17380582683/r9s)&#xff0c;大家可以在容器里&#xff0c;手动打出完整的rom包zip文件。…...

    2024/5/2 2:39:28
  4. 汽车疲劳测试试验平台技术要求(北重厂家)

    汽车疲劳测试试验平台技术要求通常包括以下几个方面&#xff1a; 车辆加载能力&#xff1a;测试平台需要具备足够的承载能力&#xff0c;能够同时测试多种车型和不同重量的车辆。 动力系统&#xff1a;测试平台需要具备稳定可靠的动力系统&#xff0c;能够提供足够的力和速度来…...

    2024/5/3 8:56:17
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/4 23:54:56
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/4 23:54:56
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/6 9:21:00
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/4 23:55:16
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/4 23:55:06
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/4 23:55:01
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57