Windows中多线程的基础知识1——互斥对象

目录

  • 1 多线程的基本概念
    • 1.1 进程
      • 一、程序和进程的概念
      • 二、进程组成
      • 三、进程地址空间
    • 1.2 线程
      • 一、线程组成
      • 二、线程运行
      • 三、线程创建函数
    • 1.3 多进程与多线程并发
      • 一、多进程并发
      • 二、多线程并发
  • 2 线程同步
    • 2.1 一个经典的线程同步问题
    • 2.2 利用互斥对象实现线程同步
      • 一、创建互斥对象
      • 二、获得互斥对象所有权
      • 三、释放互斥对象的所有权
      • 四、如何利用互斥对象实现线程同步
    • 2.3 利用互斥对象的代码实例

1 多线程的基本概念

1.1 进程

一、程序和进程的概念

 程序是计算机指令的集合,它以文件的形式存储在磁盘上。而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。进程是资源申请、调度和独立运行的单位。

二、进程组成

 进程由两部分组成:
 1.操作系统用来管理进程的内核对象
 内核对象是系统用来存放关于进程的统计信息的地方。内核对象是操作系统内部分配的一个内存块,该内存块是一种数据结构,其成员负责维护对象的各种信息。由于内核对象的数据结构只能被内核访问使用,因此应用程序在内存中无法找到该数据结构,并直接改变其内容,只能通过Windows提供的一些函数来对内核对象进行操作。
 2.地址空间
 它包含所有可执行模块或DLL模块的代码和数据。另外,它也包含动态内存分配的空间,例如线程的栈(stacks)和堆(heap)分配空间。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。也就说,真正完成代码执行的是线程,而进程只是线程的容器,或者说线程的执行环境。
 单个进程可以包含多个线程,这些线程都“同时”执行进程地址空间中的代码,每个进程至少拥有一个线程。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,成为主线程,也就是执行main或者WinMain函数的线程。此后,主线程可以创建其他线程。

三、进程地址空间

 系统赋予每个进程独立的虚拟地址空间。对于32位进程来说,这个空间地址是4GB。因为对32位指针来说,它能寻址的范围是2^32,即4GB。每个进程都有它自己的私有地址空间。

1.2 线程

一、线程组成

 线程由两部分组成:
 (1)线程的内核对象。操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
 (2)线程栈(stack)。它用于维护线程在执行代码时需要的所有函数参数和局部变量。
 当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。
 线程总是在某个进程环境中创建。线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易的通信。

二、线程运行

 操作系统为每一个运行线程安排一定的CPU时间——时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间片内运行,因时间片相当短,因此给用户的感觉就好像多个线程是同时运行一样。如果计算机拥有多个CPU,线程就能真正意义上同时运行了。

三、线程创建函数

创建线程可以使用系统提供的API函数:CreateThread来完成,该函数的原型声明如下所述:

HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId
);

  1. 返回值:返回线程句柄
    “句柄” 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;有句柄的对象一般都是系统级别的对象(或叫内核对象);之所以给我们的是句柄而不是指针,目的只有一个:“安全”;似乎通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后,我们也就到此为止很难了解更多了。
    不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.
    既然 CreateThread 可以返回一个句柄,说明线程属于 “内核对象”。实际上不管线程属于哪个进程, 它们在系统中是平等的;在优先级相同的情况下, 系统会在相同的时间间隔内来运行一下每个线程, 不过这个间隔很小很小, 以至于让我们误以为程序是在不间断地运行。这时你应该有一个疑问: 系统在去执行其他线程的时候, 是怎么记住前一个线程的数据状态的?有这样一个结构 TContext, 它基本上是一个 CPU 寄存器的集合,线程是数据就是通过这个结构切换的。
  2. 参数2:堆栈大小
    CreateThread 的第二个参数是分配给线程的堆栈大小。每个线程都有自己独立的堆栈(也拥有自己的消息队列)。什么是堆栈? 其实堆是堆、栈是栈, 有时 “栈” 也被叫做 “堆栈”。它们都是进程中的内存区域, 主要是存取方式不同(栈:先进后出; 堆:先进先出)。
    现在我们知道了线程有自己的 “栈”, 并且在建立线程时可以分配栈的大小。如果这个值都是 0, 这表示使用系统默认的大小, 默认和主线程栈的大小一样, 如果不够用会自动增长;那主线程的栈有多大? 这个值是可以设定的: Project -> Options -> Linking
    在这里插入图片描述
  3. 参数3:指向函数的指针
    指向应用程序定义的LPTHREAD_START_ROUTINE类型的函数的指针,这个函数将由新线程执行,表明新线程的起始地址。线程入口函数的原型是:
    DWORD WINAPI ThreadProc( __in LPVOID lpParameter);
    请注意,线程入口函数是有返回值的,通过GetExitCodeThread 可以获取指定线程的终止状态码。
  4. 参数4:
    线程入口函数的参数是个无类型指针(Pointer), 用它可以指定任何数据。
  5. 参数5:启动选项
    CreateThread 的倒数第二个参数 dwCreationFlags(启动选项) 有两个可选值:
    0: 线程建立后立即执行入口函数;
    CREATE_SUSPENDED: 线程建立后会挂起等待。
  6. 参数6:输出线程ID
    CreateThread 的最后一个参数是 “线程的 ID”;既然可以返回句柄, 为什么还要输出这个 ID? 原因是:
    (1) 线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等。
    (2) ID 比句柄更轻便.在主线程中 GetCurrentThreadId、MainThreadID获取的都是主线程的 ID。

1.3 多进程与多线程并发

一、多进程并发

 使用多进程并发是将一个应用程序划分为多个独立的进程(每个进程只有一个线程),这些独立的进程间可以互相通信,共同完成任务。由于操作系统对进程提供了大量的保护机制,以避免一个进程修改了另一个进程的数据,使用多进程比使用多线程更容易写出相对安全的代码。但是这也造就了多进程并发的两个缺点:
 (1)在进程间的通信,无论是使用信号、套接字,还是文件、管道等方式,其使用要么比较复杂,要么就是速度较慢或者两者兼而有之。
 (2)运行多个进程的开销很大,操作系统要分配很多的资源来对这些进程进行管理。
 当多个进程并发完成同一个任务时,不可避免的是:操作同一个数据和进程间的相互通信,上述的两个缺点也就决定了多进程的并发并不是一个好的选择。所以就引入了多线程的并发。

二、多线程并发

 多线程并发指的是在同一个进程中执行多个线程。
 优点:有操作系统相关知识的应该知道,线程是轻量级的进程,每个线程可以独立的运行不同的指令序列,但是线程不独立的拥有资源,依赖于创建它的进程而存在。也就是说,同一进程中的多个线程共享相同的地址空间,可以访问进程中的大部分数据,指针和引用可以在线程间进行传递。这样,同一进程内的多个线程能够很方便的进行数据共享以及通信,也就比进程更适用于并发操作。
 缺点:由于缺少操作系统提供的保护机制,在多线程共享数据及通信时,就需要程序员做更多的工作以保证对共享数据段的操作是以预想的操作顺序进行的,并且要极力的避免死锁(deadlock)。
 由于以上原因,我们在编程中应当经常采用多线程来解决编程问题,尽量避免创建新的进程。

2 线程同步

2.1 一个经典的线程同步问题

 多线程编程中,如果多个线程需要访问共享资源,就需要进行线程间的同步处理。那么什么是线程同步呢?线程同步是指当多个线程共享同一个资源,不会受到其他线程的干扰;或者说,线程同步指的是线程之间“协同”,即线程之间按照规定的先后次序运行。
 线程同步的概念和其他“同步”概念不太一致,请区分线程同步和下面两个同步概念:
  设备同步:在不同的设备之间规定一个共同的参考时间
  数据库/文件同步:在不同的数据库之间保持数据一致
 下面,我们创建一个多线程的程序,看看一个有趣的问题.

#include <iostream>
#include<windows.h>DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter);
DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter);int iIndx = 0;
int iTickets = 100;//创建进程的时候,系统会自动创建进程的第一个线程,成为主线程,也就是
//执行main函数的进程
int main()
{HANDLE hThread1;HANDLE hThread2;//创建线程hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);CloseHandle(hThread1);CloseHandle(hThread2);while (true)if (iTickets > 0)Sleep(100);else{getchar();return 0;}
}//
DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter)
{while (true){if (iTickets > 0)std::cout << "thread1 sell tickets:" << iTickets-- << std::endl;elsebreak;}return 0;
}DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter)
{while (true){if (iTickets > 0)std::cout << "thread2 sell tickets:" << iTickets-- << std::endl;elsebreak;}return 0;
}

 首先,在main函数中我们创建了两个线程,线程的入口函数分别是Fun1Proc和Fun2Proc,在调用CreateThread函数时,第5个参数设置为0,表明线程立即执行。
 这里需要注意一点的是,我们在main函数中调用了CloseHandle;可能有人要问,为什么刚刚创建了线程,现在又关闭了呢?原因是CloseHandle并没有终止新创建的线程的执行,而仅仅表示主进程对新创建的线程的引用不感兴趣,因此将它关闭。另一方面,当关闭该句柄时,系统会递减该线程内核对象的使用计数。当创建的这个新线程执行完毕以后,系统也会递减该线程内核对象的使用计数。当使用计数为0时,系统就会释放该线程内核对象。如果没有关闭线程句柄,系统会一致保持着对线程内核对象的引用,这样,即使该线程执行完毕,它的引用计数仍不会为0。这样该线程内核对象也就不会被释放,只有等到进程终止时,系统才会清理这些残留的对象。
 运行这段代码,结果如下:
在这里插入图片描述
 在图中,竟然出现了线程2卖了倒数第14张票,紧接着线程1卖了倒数第15张票的情况,这说明两个线程函数在访问iTickets全局变量时,出现了同步问题。具体来说:当线程1卖倒数第15张票的时候,正准备要打印到屏幕上;然后此时系统将时间片交给了线程2,线程2卖了倒数第14张票,并且顺利打印;恰在此时,系统又将流程切换到了流程1,流程1继续打印卖了倒数第15张票这个信息。
 以上问题实际上是经典的线程同步问题,我们要让“线程之间“协同”,即线程之间按照规定的先后次序运行”,否则对共享资源的访问就可能出错。

2.2 利用互斥对象实现线程同步

 为了解决上述线程同步问题,我们可以使用互斥对象。
 互斥对象(mutex)属于内核对象,它能确保线程拥有单个资源的互斥访问权。互斥对象包含一个使用数量,一个线程ID和一个计数器。其中ID用于标识系统中哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

一、创建互斥对象

 创建互斥对象的函数为

HANDLE WINAPI CreateMutex(
__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,
__in_opt LPCTSTR lpName
);

 lpMutexAttributes:可以给该对象传递NULL值,让互斥对象使用默认的安全性。
 bInitialOwner:BOOL类型,指定互斥对象的初始拥有者。如果为真,则创建这个互斥对象的线程获得该对象的所有权;否则,该线程将不获得所创建的互斥对象的所有权。
 lpName是互斥对象的名称,如果参数为NULL,则创建了一个匿名对象。

 在互斥对象中,有一个重要概念:如果所有线程都不拥有互斥对象,那么就称互斥对象处于有信号状态;但凡某个线程拥有了互斥对象的所有权,就称互斥对象处于无信号状态。

二、获得互斥对象所有权

 线程必须主动请求互斥对象的使用权才可能获得该所有权,这可以通过下面函数实现

DWORD WINAPI WaitForSingleObject(__in  HANDLE hHandle,__in  DWORD dwMilliseconds
);

 hHandle:所请求对象的句柄。一旦互斥对象处于有信号状态,则该函数就返回。如果互斥对象始终处于无信号的状态,则该函数就会一直等待,这样就会暂停线程的执行。
 dwMilliseconds:指定的等待时间间隔,以毫秒为单位。如果指定的时间间隔已过,即使所请求的对象仍处于无信号状态,WaitForSingleObject函数也会返回。如果此参数设置为0,那么函数将测试该对象的状态并立刻返回;如果此参数设置为INFINITE,则该函数会永远等待,直到等待的对象处于有信号状态才会返回。

三、释放互斥对象的所有权

 当线程对共享资源访问结束后,应释放该互斥对象的所有权,也就是让互斥对象处于有信号状态,这可以通过下面函数实现

BOOL WINAPI ReleaseMutex( __in  HANDLE hMutex);

 ReleaseMutex函数只有一个HANDLE类型的参数,即需要释放的互斥对象的句柄。该函数的返回值是BOOL类型,如果函数调用成功,返回非0值,否则返回0值。
 另外需要注意,对互斥对象来讲,哪个线程拥有所有权,哪个线程才能释放这个对象。比如说A线程拥有互斥对象,你就不能在B线程内释放这个对象。另外操作系统一旦发现某个线程已经终止,它就会自动将该线程所拥有的互斥对象的线程ID设为0,并将其计数归0。

四、如何利用互斥对象实现线程同步

 基本思路是这样的:在程序中创建一个全局的互斥对象(当然,你可以使用单例模式,将全局对象封装在函数中,转为局部静态变量),在每个线程中,如果有需要访问的共享资源,就在访问共享资源之前通过WaitForSingleObject获得互斥对象的所有权,在访问之后,再用ReleaseMutex释放互斥对象的所有权。这样,每个线程在访问共享资源时,都会“排斥”其他线程获得互斥对象所有权,只有等这个线程访问完毕后,下一个线程才能获得互斥对象所有权,才能进来访问共享资源。这样,也就实现了线程同步了。

2.3 利用互斥对象的代码实例

 接下来,让我们看看利用互斥对象的具体使用例程

#include <iostream>
#include<windows.h>DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter);
DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter);int iIndx = 0;
int iTickets = 100;
HANDLE hMutex;//创建进程的时候,系统会自动创建进程的第一个线程,成为主线程,也就是
//执行main函数的进程
int main()
{HANDLE hThread1;HANDLE hThread2;//创建互斥对象hMutex = CreateMutex(NULL, FALSE, NULL);//创建线程hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);CloseHandle(hThread1);CloseHandle(hThread2);while (true){if (iTickets > 0)Sleep(100);else{getchar();return 0;}}
}//
DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter)
{while (true){WaitForSingleObject(hMutex, INFINITE);if (iTickets > 0){std::cout << "thread1 sell tickets:" << iTickets-- << std::endl;}elsebreak;ReleaseMutex(hMutex);}return 0;
}DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter)
{while (true){WaitForSingleObject(hMutex, INFINITE);if (iTickets > 0){std::cout << "thread2 sell tickets:" << iTickets-- << std::endl;}elsebreak;ReleaseMutex(hMutex);}return 0;
}

 在上述main函数中,我们首先用CreateMutex创建了互斥对象,请注意其第二个参数为FALSE,所以主线程不拥有该对象。然后,在每个线程函数中都使用了WaitForSingleObject\ReleaseMutex完成了各线程对共享资源的同步访问要求。我们来看结果,同步问题已经解决。
在这里插入图片描述
 下一节,我们将介绍:Windows中多线程的基础知识——2事件对象。

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

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

相关文章

Redis 集群

1. 是什么 1.1 定义 由于数据量过大&#xff0c;单个Master复制集难以承担&#xff0c;因此需要对多个复制集进行集群&#xff0c;形成水平扩展每个复制集只负责存储整个数据集 的一部分&#xff0c;这就是Redis的集群&#xff0c;其作用是提供在多个Redis节点间共享数据的程序…

postman9.12.汉化版(附有下载链接)

想用英文版本的可以直接点击下载最新版本 这里直接付上9.12.2版本的下载链接&#xff0c;如果大家要下载别的版本&#xff0c;可以直接修改链接里面的版本号即可 &#xff0c;下面是汉化包下载 链接&#xff1a;https://pan.baidu.com/s/1izK3HfqlfXJdq6KIYeJ2zw?pwdpetk 提…

合并两个有序链表(每日一题)

“路虽远&#xff0c;行则将至” ❤️主页&#xff1a;小赛毛 ☕今日份刷题&#xff1a;合并两个有序链表 题目描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例1&#xff1a; 输入&#xff1a;l1 …

HTML5-4-表单

文章目录 表单属性表单标签输入元素文本域&#xff08;Text Fields&#xff09;密码字段单选按钮&#xff08;Radio Buttons&#xff09;复选框&#xff08;Checkboxes&#xff09;按钮&#xff08;button&#xff09;提交按钮(Submit)label标签 文本框&#xff08;textarea&am…

【Redis】redis入门+java操作redis

目录 一、Redis入门 1.1 Redis简介 1.2 Redis下载与安装 1.2.1 下载 1.2.2 linux安装 1.2.3 windows安装 1.3 Redis服务启动与停止 1.3.1 linux启动、停止Redis服务 1.3.2 windows启动、停止Redis服务 1.4 修改Redis启动密码 1.4.1 Linux修改设置 1.4.2 windows设…

淘宝天猫API技术解析,实现关键词搜索淘宝商品(商品详情接口等)批量获取,可高并发

淘宝和天猫提供了官方API接口&#xff0c;开发者可以通过这些接口获取商品信息、进行交易操作等。下面我将简要介绍如何使用淘宝API进行关键词搜索商品并批量获取商品详情。 首先&#xff0c;需要了解淘宝API的几个主要接口&#xff1a; 搜索接口&#xff1a;用于根据关键词搜…

云原生Kubernetes:二进制部署K8S多Master架构(三)

目录 一、理论 1.K8S多Master架构 2.配置master02 3.master02 节点部署 4.负载均衡部署 二、实验 1.环境 2.配置master02 3.master02 节点部署 4.负载均衡部署 三、总结 一、理论 1.K8S多Master架构 (1) 场景 Kubernetes作为容器集群系统&#xff0c;通过健康检查重…

诚实守信、专业严谨、合规自律、公开透明——嘉泰实业

每一次暖心的沟通都是一次公益,真诚不会因为它的渺小而被忽略;每一声问候都是一次公益,善意不会因为它的普通而被埋没。熟悉嘉泰实业的人都知道,这家企业不但擅长在金融理财领域里面呼风唤雨,同时也非常擅长在公益事业当中践行,属于企业的责任心,为更多有困难的群体带来大爱的传…

rpm打包

文章目录 rpm打包 1. rpm打包步骤0&#xff09;准备工作&#xff1a;安装打包工具rpm-build和rpmdevtools&#xff08;1&#xff09;在线安装&#xff08;2&#xff09;离线安装 1&#xff09;创建初始化目录2&#xff09;准备打包内容3&#xff09;编写打包脚本 spec文件4&…

基于Hugo 搭建个人博客网站

目录 1.环境搭建 2.生成博客 3.设置主题 4.将博客部署到github上 1.环境搭建 1&#xff09;安装Homebrew brew是一个在 macOS 操作系统上用于管理软件包的包管理器。类似于centos下的yum或者ubuntu下的apt&#xff0c;它允许用户通过命令行安装、更新和管理各种软件工具、…

8月《中国数据库行业分析报告》已发布,聚焦数据仓库、首发【全球数据仓库产业图谱】

为了帮助大家及时了解中国数据库行业发展现状、梳理当前数据库市场环境和产品生态等情况&#xff0c;从2022年4月起&#xff0c;墨天轮社区行业分析研究团队出品将持续每月为大家推出最新《中国数据库行业分析报告》&#xff0c;持续传播数据技术知识、努力促进技术创新与行业生…

用户体验地图是什么?UX设计心得分享

大家好&#xff0c;我是设计师l1m0身。本篇文章是关于UX设计中的用户体验地图。 对于新手设计师来说&#xff0c;建立用户体验地图会有一些难度。本篇文章中&#xff0c;我会以简单、易懂的语言分享UX设计师如何制作用户体验地图&#xff0c;希望对你的日常项目体验提升有所帮…

【计算机网络】http协议

目录 前言 认识URL URLEncode和URLDecode http协议格式 http方法 GET POST GET与POST的区别 http状态码 http常见header 简易的http服务器 前言 我们在序列化和反序列化这一章中&#xff0c;实现了一个网络版的计算器。这个里面设计到了对协议的分析与处…

华为---OSPF协议优先级、开销(cost)、定时器简介及示例配置

OSPF协议优先级、开销、定时器简介及示例配置 路由协议优先级&#xff1a;由于路由器上可能同时运行多种动态路由协议&#xff0c;就存在各个路由协议之间路由信息共享和选择的问题。系统为每一种路由协议设置了不同的默认优先级&#xff0c;当在不同协议中发现同一条路由时&am…

前端加密方式

前端加密 1.不可逆加密2.可逆加密a.对称加密b.非对称加密&#xff08;本文重点&#xff09;a.含义&#xff1a;b.过程理解&#xff1a;c.项目中使用&#xff1a; 总结&#xff1a;参考地址 目前搜索前端加密是可以看到有非常非常多的方法的&#xff0c;这里我们需要对其分类总结…

微服务主流框架概览

微服务主流框架概览 目录概述需求&#xff1a; 设计思路实现思路分析1.HSF2.Dubbo 3.Spring Cloud5.gRPC Service mesh 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a be…

Cannot read property ‘database‘ of undefined解决办法

PS&#xff1a;在最近项目部署的时候&#xff0c;后台遇到如下的报错&#xff0c;显示数据库未定义&#xff0c;研究了半天没有找到原因&#xff0c;但是能解决掉这个报错 TypeError: Cannot read property ‘database’ of undefined 我们查看下具体的文件目录 我们需要返回…

【链表OJ 11】复制带随机指针的链表

前言: &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&#x1f4a5; ✨✨刷题专栏:http://t.csdn.cn/UlvTc ⛳⛳本篇内容:力扣上链表OJ题目 目录 leetcode138. 复制带随机指针的链表 1. 问题描述 2.代码思路: 2.1拷贝节点插入到…

【LeetCode】328. 奇偶链表

328. 奇偶链表&#xff08;中等&#xff09; 思路 如果链表为空&#xff0c;则直接返回链表。 对于原始链表&#xff0c;每个节点都是奇数节点或偶数节点。头节点是奇数节点&#xff0c;头节点的后一个节点是偶数节点&#xff0c;相邻节点的奇偶性不同。因此可以将奇数节点和偶…

Unity AssetBundle(1):Assets打包和依赖(Dependencies)

对Unity5.x后的AssetBundle依赖机制有了一点理解&#xff0c;创建了一个项目验证 github:GeWenL / AssetBundlePro AbScene.unity 资源有哪些&#xff1f; Some common types of Asset assetbundle打包命令是 BuildPipeline.BuildAssetBundles ,格式有&#xff1a; 引用&…