C#笔记13 线程同步概念及其实现,详解lock,Monitor,Mutex代码用法

同步的概念

在我们学会在C#中使用线程之后,我们拥有了把一个程序中的不同代码段在不同线程中运行的能力,可以说此时我们已经能够做到让他们分别执行,异步执行。

对于我们的桌面端程序,使用多线程可以让我们在后台进行操作的时候保持用户界面的响应。

对于服务器应用程序,多线程可以让我们单独接收处理每个客户端发来的请求。否则在完全处理完一个请求之前将无法响应其他请求。

但是多线程完全异步意味着线程之间不存在互相配合,此时就必须协调资源的分配和管理,互相之间必须要有协调。

如果多个线程同时访问修改同一个数据,很可能造成数据损坏。

这在数据库程序里的读写操作需要保障安全有异曲同工之处。

线程同步:

是指并发线程,高效有序的访问共享资源的技术。

同步:

同步是指某一时刻,只有一个线程能访问资源,只有当资源所有者放弃了资源或者代码的占有,其他线程才能使用这些资源。

前台线程和后台线程的区别

前台线程只要没有结束,应用程序就不会结束,如果前台线程全部结束,后台线程将自动结束,而不考虑是否完成。

后台线程的属性IsBackgroud需要被设置为True。

 实现方式

lock关键字

lock关键字用于确保代码块同一时间只会有一个线程在运行,不会被其他线程中断。

它的使用形式为:以lock关键字开头,有一个作为参数的对象(引用类型数据),后跟一个代码块。此代码块一次只能由一个线程执行。

using System;
using System.Threading;class Program
{private static int counter = 0;private static readonly object lockObject = new object();static void Main(){Thread thread1 = new Thread(IncrementCounter);Thread thread2 = new Thread(IncrementCounter);thread1.Start();thread2.Start();thread1.Join();thread2.Join();Console.WriteLine($"最终计数器值: {counter}");}static void IncrementCounter(){for (int i = 0; i < 100000; i++){// 使用 lock 来确保此代码块在任意时刻只能由一个线程执行lock (lockObject){counter++;}}}
}

lock后的参数可以是任意引用类型的对象,但是通常我们用来表示需要进行进程同步的资源,如果一个资源将被多个线程使用,使用对该资源的引用作为lock参数,只要在lock后的代码块内使用该资源,就会是线程安全的,因为这保证了一时间只有一个线程访问该资源。

注意:如果锁定的对象被其他不受我们控制的同步语句锁定,那么就会导致死锁,尤其是public公共类型或者字符串这类会被公共语言库暂留,只要有任意一个线程锁定了这个字符串,就会锁定所有以此字符串为锁的代码块。锁定这类不受我们控制的对象实例,这是非常危险的。

最好锁定:不会被暂留的私有或者受保护成员。

  • 简单易用:使用 lock 可以快速实现线程同步。
  • 自动化异常处理lock 结构会自动释放锁,即使在发生异常时,也能够保证锁被正确释放。
  • 对象锁lock 需要一个引用类型的对象作为锁标识,这个对象可以是任何引用类型的实例。

 理解上很简单,这个代码块有一把代表了资源的锁,这把锁如果我们进入了代码块(使用了资源),就会锁上。这样其他线程就进不来了,同样的,如果选的锁不好,就会导致很多代码块等一个没必要等的锁(资源)。

lock本质是由下面的Monitor实现的。

Monitor类

此类提供了同步对象的访问机制。

它可以向一个线程授予对象锁用来控制对对象的访问。对象锁提供限制访问代码块的能力。当一个线程获得了这个锁之后,其他线程不能获得该锁。

功能:

  • 与某个对象关联
  • 未绑定的,可以从任何上下文直接调用它。
  • 不能创建该类实例

方法

Enter:请求获取锁。
Exit:释放锁。
Wait:释放锁并阻塞当前线程,直到接收到另一个线程的通知。
Pulse:通知正在等待的线程它们可以继续。
PulseAll:通知所有等待的线程它们可以继续。

using System;
using System.Threading;class Program
{private static readonly object lockObject = new object(); // 任意对象,用于锁定static void Main(){Thread thread1 = new Thread(DoWork);Thread thread2 = new Thread(DoWork);thread1.Start();thread2.Start();thread1.Join();thread2.Join();Console.WriteLine("所有线程已完成工作。");}static void DoWork(){// 使用 Monitor 的未绑定方法Monitor.Enter(lockObject); // 这里直接调用静态方法 Entertry{// 临界区代码Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} 正在工作...");Thread.Sleep(1000); // 模拟一些工作}finally{Monitor.Exit(lockObject); // 确保在任何情况下都释放锁}}
}

用Monitor模拟售票系统

using System;
using System.Threading;class Program
{private static int totalTickets = 10; // 共享资源:总票数private static readonly object lockObject = new object(); // 用于同步的锁对象static void Main(){// 创建四个线程模拟购票Thread thread1 = new Thread(BuyTicket);Thread thread2 = new Thread(BuyTicket);Thread thread3 = new Thread(BuyTicket);Thread thread4 = new Thread(BuyTicket);// 启动线程thread1.Start();thread2.Start();thread3.Start();thread4.Start();// 等待所有线程完成thread1.Join();thread2.Join();thread3.Join();thread4.Join();Console.WriteLine("所有线程已完成购票。");}static void BuyTicket(){while (true){// 使用 Monitor 进行同步Monitor.Enter(lockObject);try{if (totalTickets > 0){// 模拟购票过程Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 正在购买第 {totalTickets} 张票...");totalTickets--; // 购买一张票Thread.Sleep(100); // 模拟一些操作的延迟}else{// 票已售完,退出循环Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 发现票已售完。");break;}}finally{// 确保释放锁Monitor.Exit(lockObject);}}}
}

标准输出:线程 5 正在购买第 10 张票...
线程 5 正在购买第 9 张票...
线程 5 正在购买第 8 张票...
线程 5 正在购买第 7 张票...
线程 5 正在购买第 6 张票...
线程 5 正在购买第 5 张票...
线程 5 正在购买第 4 张票...
线程 5 正在购买第 3 张票...
线程 5 正在购买第 2 张票...
线程 5 正在购买第 1 张票...
线程 5 发现票已售完。
线程 3 发现票已售完。
线程 6 发现票已售完。
线程 4 发现票已售完。
所有线程已完成购票。

 作为一种锁机制,最好不要锁定公开的或者会被暂留的字符串。

锁定当前的对象实例也是不可靠的,因为可能其他代码只要获得该对象实例的引用就可以锁定这个实例。

Monitor.enter(this);

 wait方法:

拥有锁的线程放弃资源并选择等待,可以被下面的通知唤醒。

Monitor.Enter(lockObject);
try
{while (conditionNotMet){Monitor.Wait(lockObject); // 释放锁并等待通知}// 执行相关操作
}
finally
{Monitor.Exit(lockObject); // 确保释放锁
}

pulse方法

唤醒一个在等待此资源的线程。

唤醒的线程不立即获得锁,它会在被唤醒后,重新尝试获取锁。这是因为 Pulse 只是通知一个线程,它仍然需要通过重新获取锁来继续执行。

Monitor.Enter(lockObject);
try
{// 执行相关操作Monitor.Pulse(lockObject); // 唤醒一个等待的线程
}
finally
{Monitor.Exit(lockObject); // 确保释放锁
}

 pulseAll方法

同上,但是唤醒所有。

用于需要通知所有等待线程某个状态变化的场景,比如通知多个消费者,数据已经准备好。

Monitor.Enter(lockObject);
try
{// 执行相关操作Monitor.PulseAll(lockObject); // 唤醒所有等待的线程
}
finally
{Monitor.Exit(lockObject); // 确保释放锁
}

TryEnter方法

public static bool TryEnter(object obj);
public static bool TryEnter(object obj, int millisecondsTimeout);
public static bool TryEnter(object obj, TimeSpan timeout);

 尝试获取对象锁,可以设定在指定时间内获取锁。返回获取的成功与否的布尔值。

具有超时的等待

public static bool Wait(object obj, int millisecondsTimeout);
public static bool Wait(object obj, TimeSpan timeout);

让线程释放对象锁并等待一定时间自动唤醒(不会立刻开始执行,会重新尝试获得锁)。 

Monitor.Enter(lockObject);
try
{while (conditionNotMet){if (!Monitor.Wait(lockObject, TimeSpan.FromSeconds(5))) // 等待5秒{Console.WriteLine("等待超时");break;}}// 执行相关操作
}
finally
{Monitor.Exit(lockObject); // 确保释放锁
}

 

生产消费者模型

using System;
using System.Threading;class Program
{private static readonly object lockObject = new object();private static bool dataAvailable = false;static void Main(){Thread producer = new Thread(Produce);Thread consumer = new Thread(Consume);producer.Start();consumer.Start();producer.Join();consumer.Join();Console.WriteLine("生产者-消费者过程完成。");}static void Produce(){Monitor.Enter(lockObject);try{Console.WriteLine("生产者:正在生产数据...");Thread.Sleep(2000); // 模拟生产过程dataAvailable = true;Monitor.Pulse(lockObject); // 唤醒等待的消费者线程Console.WriteLine("生产者:数据已生产,通知消费者。");}finally{Monitor.Exit(lockObject); // 确保释放锁}}static void Consume(){Monitor.Enter(lockObject);try{while (!dataAvailable){Console.WriteLine("消费者:等待数据...");bool signaled = Monitor.Wait(lockObject, TimeSpan.FromSeconds(3)); // 等待生产者通知,最多3秒if (!signaled){Console.WriteLine("消费者:等待超时,尝试重新获取锁...");}}Console.WriteLine("消费者:获取数据并进行消费。");}finally{Monitor.Exit(lockObject); // 确保释放锁}}
}

 Mutex类

同步基元。基本想法和作用类似于我们的Monitor类,但是它可以实现跨进程的线程同步。

它只向一个线程授予对共享资源的获取权限,如果一个线程获取了互斥体,则其他想要这个互斥体的线程将被挂起。

它的本质是对win32的封装,比Monitor更强大,但是不适合用于进程内的同步,因为这样会浪费一些系统转换的资源。Monitor会更好的利用资源。

基本使用

和Monitor类似,在进程内,使用Mutex实例获取锁,或者释放锁。

这里和Monitor不同的是,这里没有Enter方法来获取锁,就是直接阻塞然后等待锁(资源)。Monitor获取锁之后还能使用Wait方法等待资源,同时让出锁,但是我们这里就只直接释放锁即可。

可以这么理解:Monitor可以获取,可以释放,可以释放在等待重新获取,可以通知其他正在等待这个资源的线程。

Mutex也可以获取,也可以释放,可以释放再等待其他资源(注意是其他资源),可以一次性等待多个资源。

创建 Mutex 对象:

Mutex mutex = new Mutex(); 创建一个新的 Mutex 对象。所有需要同步的线程共享这个 Mutex 实例。
请求获得锁 (WaitOne):

mutex.WaitOne(); 请求当前线程获得 Mutex 锁。如果锁已经被其他线程占用,当前线程将会阻塞,直到锁被释放。
释放锁 (ReleaseMutex):

mutex.ReleaseMutex(); 释放当前线程持有的锁,使其他等待的线程能够获得锁。

using System;
using System.Threading;class Program
{// 创建一个 Mutex 对象private static Mutex mutex = new Mutex();static void Main(){// 启动多个线程来模拟并发访问for (int i = 0; i < 5; i++){Thread thread = new Thread(AccessSharedResource);thread.Name = $"线程 {i + 1}";thread.Start();}}static void AccessSharedResource(){Console.WriteLine($"{Thread.CurrentThread.Name} 正在等待进入临界区...");// 请求获得 Mutex 锁mutex.WaitOne();try{Console.WriteLine($"{Thread.CurrentThread.Name} 已进入临界区。");// 模拟对共享资源的访问Thread.Sleep(2000);Console.WriteLine($"{Thread.CurrentThread.Name} 正在离开临界区。");}finally{// 释放 Mutex 锁mutex.ReleaseMutex();Console.WriteLine($"{Thread.CurrentThread.Name} 已释放锁。");}}
}

输出结果: 

线程 3 正在等待进入临界区...
线程 1 正在等待进入临界区...
线程 2 正在等待进入临界区...
线程 3 已进入临界区。
线程 4 正在等待进入临界区...
线程 5 正在等待进入临界区...
线程 3 正在离开临界区。
线程 3 已释放锁。
线程 5 已进入临界区。
线程 5 正在离开临界区。
线程 5 已释放锁。
线程 4 已进入临界区。
线程 4 正在离开临界区。
线程 4 已释放锁。
线程 2 已进入临界区。
线程 2 正在离开临界区。
线程 2 已释放锁。
线程 1 已进入临界区。
线程 1 正在离开临界区。
线程 1 已释放锁。

 带超时的Mutex等待锁:

using System;
using System.Threading;class Program
{private static Mutex mutex = new Mutex();static void Main(){for (int i = 0; i < 3; i++){Thread thread = new Thread(TryAccessSharedResource);thread.Name = $"线程 {i + 1}";thread.Start();}}static void TryAccessSharedResource(){Console.WriteLine($"{Thread.CurrentThread.Name} 正在等待进入临界区...");// 尝试获取锁,超时时间为 3 秒if (mutex.WaitOne(TimeSpan.FromSeconds(3))){try{Console.WriteLine($"{Thread.CurrentThread.Name} 已进入临界区。");Thread.Sleep(2000); // 模拟操作}finally{mutex.ReleaseMutex();Console.WriteLine($"{Thread.CurrentThread.Name} 已释放锁。");}}else{Console.WriteLine($"{Thread.CurrentThread.Name} 无法在指定时间内获得锁。");}}
}

上面等待锁的逻辑没有变化,只是尝试获取锁,并指定最大等待时间为 3 秒。如果在 3 秒内没有获取到锁,返回 false,否则返回 true 并继续执行。不会被一直阻塞,会提供bool值以供走到另外一个分支。

跨进程使用Mutex

需要使用命名Mutex才行,跨进程即跨应用程序,所以已经超越了我们命名全局变量等的作用范围。

创建Mutex的进程:

using System;
using System.Threading;class Program
{static void Main(){// 创建一个命名的 Mutex,用于跨进程同步using (Mutex mutex = new Mutex(false, "Global\\MyNamedMutex")){Console.WriteLine("等待获取跨进程的 Mutex...");// 尝试获取 Mutex 锁if (mutex.WaitOne(TimeSpan.FromSeconds(10))){try{Console.WriteLine("获取到跨进程的 Mutex,正在执行操作...");Thread.Sleep(5000); // 模拟操作}finally{mutex.ReleaseMutex();Console.WriteLine("已释放跨进程的 Mutex。");}}else{Console.WriteLine("无法在指定时间内获取跨进程的 Mutex。");}}}
}

另外一个使用这个Mutex的进程: 

using System;
using System.Threading;class Program
{static void Main(){try{// 打开已经存在的命名 MutexMutex existingMutex = Mutex.OpenExisting("Global\\MyNamedMutex");Console.WriteLine("成功打开已存在的命名 Mutex。");}catch (WaitHandleCannotBeOpenedException){Console.WriteLine("命名 Mutex 不存在。");}}
}

 

注意事项

开销较大:Mutex 通常比 lock 或 Monitor 开销更大,因为它是内核对象,需要更多的系统资源。仅在需要跨进程同步时才使用 Mutex,否则可以考虑使用 lock 或 Monitor。
防止死锁:确保每次 WaitOne 成功后,使用 ReleaseMutex 释放锁,以防止死锁。
使用 using 语句:在使用命名 Mutex 时,推荐使用 using 语句以确保在异常情况下也能正确释放资源。

Mutex进阶

构造函数

布尔值指示当前线程是否立刻获得此Mutex。string用于指示Mutex命名。

还有一个带有一个输出参数的构造函数,用于输出当前命名的Mutex是否是新的,如果该Mutex已经存在,则返回False,否则为True。

Mutex mutex = new Mutex();
Mutex mutex = new Mutex(true);
Mutex mutex = new Mutex(false, "Global\\MyNamedMutex");bool createdNew;
Mutex mutex = new Mutex(false, "Global\\MyNamedMutex", out createdNew);
Console.WriteLine($"Mutex 是否新创建: {createdNew}");

WaitHandle是什么 

是Mutex,Semaphore等的基类,定义一种用于同步的抽象。

WaitHandle 的常用方法
WaitOne():阻塞当前线程,直到收到信号或超时。该方法用于等待单个 WaitHandle 变为有信号状态。

WaitAny(WaitHandle[]):阻塞当前线程,直到任何一个提供的 WaitHandle 变为有信号状态。它在第一个 WaitHandle 变为有信号状态时返回。

WaitAll(WaitHandle[]):阻塞当前线程,直到所有提供的 WaitHandle 都变为有信号状态。它在所有 WaitHandle 变为有信号状态后返回。

更多方法:

Close:显式关闭 Mutex,释放与之相关的所有资源。
OpenExisting:打开一个已存在的命名 Mutex,用于进程间同步。
SignalAndWait:释放一个锁,同时等待另一个锁。
WaitAll:等待所有指定的 WaitHandle 对象被释放。
WaitAny:等待任意一个指定的 WaitHandle 对象被释放。
WaitOne:等待一个 Mutex 对象被释放,这是最常用的等待方法。

WaitAll 

和WaitOne一样,要等资源,只不过同时等待所有资源。必须获得所有的锁才会停止阻塞等待。

输出结果: 

线程1 尝试获取 Mutex1...
等待所有的 Mutex 被释放...
线程2 尝试获取 Mutex2...
线程3 尝试获取 Mutex3...
线程1 已获取 Mutex1,等待 3 秒后释放...
线程2 已获取 Mutex2,等待 2 秒后释放...
线程3 已获取 Mutex3,等待 1 秒后释放...
线程3 释放 Mutex3。
线程2 释放 Mutex2。
线程1 释放 Mutex1。
所有的 Mutex 都已被释放。 

using System;
using System.Threading;class Program
{static Mutex mutex1 = new Mutex();static Mutex mutex2 = new Mutex();static Mutex mutex3 = new Mutex();static void Main(){// 启动线程来模拟不同的 Mutex 状态Thread thread1 = new Thread(ThreadProc1);Thread thread2 = new Thread(ThreadProc2);Thread thread3 = new Thread(ThreadProc3);thread1.Start();thread2.Start();thread3.Start();// 等待所有的 Mutex 被释放Console.WriteLine("等待所有的 Mutex 被释放...");bool allSignaled = WaitHandle.WaitAll(new WaitHandle[] { mutex1, mutex2, mutex3 }, 10000); // 等待最多 10 秒if (allSignaled){Console.WriteLine("所有的 Mutex 都已被释放。");}else{Console.WriteLine("等待超时或不是所有的 Mutex 都已被释放。");}thread1.Join();thread2.Join();thread3.Join();}static void ThreadProc1(){Console.WriteLine("线程1 尝试获取 Mutex1...");mutex1.WaitOne();Console.WriteLine("线程1 已获取 Mutex1,等待 3 秒后释放...");Thread.Sleep(3000);mutex1.ReleaseMutex();Console.WriteLine("线程1 释放 Mutex1。");}static void ThreadProc2(){Console.WriteLine("线程2 尝试获取 Mutex2...");mutex2.WaitOne();Console.WriteLine("线程2 已获取 Mutex2,等待 2 秒后释放...");Thread.Sleep(2000);mutex2.ReleaseMutex();Console.WriteLine("线程2 释放 Mutex2。");}static void ThreadProc3(){Console.WriteLine("线程3 尝试获取 Mutex3...");mutex3.WaitOne();Console.WriteLine("线程3 已获取 Mutex3,等待 1 秒后释放...");Thread.Sleep(1000);mutex3.ReleaseMutex();Console.WriteLine("线程3 释放 Mutex3。");}
}

WaitAny

这里主线程等待两个资源,线程1和线程2分别占有一个锁。可以看看输出结果:

等待任意一个 Mutex 被释放...
Mutex 1 已被释放。
线程1 尝试获得 Mutex1...
线程2 尝试获得 Mutex2...
线程2 已获得 Mutex2,等待 3 秒后释放...
线程2 已释放 Mutex2。

using System;
using System.Threading;class Program
{static Mutex mutex1 = new Mutex();static Mutex mutex2 = new Mutex();static void Main(){// 启动两个线程,分别尝试获取锁并在稍后释放它们Thread thread1 = new Thread(ThreadProc1);Thread thread2 = new Thread(ThreadProc2);thread1.Start();thread2.Start();// 等待任意一个锁被释放Console.WriteLine("等待任意一个 Mutex 被释放...");int index = WaitHandle.WaitAny(new WaitHandle[] { mutex1, mutex2 });Console.WriteLine($"Mutex {index + 1} 已被释放。");thread1.Join();thread2.Join();}static void ThreadProc1(){// 模拟工作Thread.Sleep(1000);Console.WriteLine("线程1 尝试获得 Mutex1...");mutex1.WaitOne();Console.WriteLine("线程1 已获得 Mutex1,等待 3 秒后释放...");Thread.Sleep(3000);mutex1.ReleaseMutex();Console.WriteLine("线程1 已释放 Mutex1。");}static void ThreadProc2(){// 模拟工作Thread.Sleep(2000);Console.WriteLine("线程2 尝试获得 Mutex2...");mutex2.WaitOne();Console.WriteLine("线程2 已获得 Mutex2,等待 3 秒后释放...");Thread.Sleep(3000);mutex2.ReleaseMutex();Console.WriteLine("线程2 已释放 Mutex2。");}
}

SignalAndWait

用于占用一该资源之后,释放的同时又获取另外一种资源。

using System;
using System.Threading;class Program
{private static Mutex mutex1 = new Mutex();private static Mutex mutex2 = new Mutex();static void Main(){Thread thread1 = new Thread(ThreadProc1);Thread thread2 = new Thread(ThreadProc2);thread1.Start();thread2.Start();thread1.Join();thread2.Join();}static void ThreadProc1(){mutex1.WaitOne();Console.WriteLine("线程1获得了 mutex1");// 释放 mutex1 并等待获取 mutex2Mutex.SignalAndWait(mutex1, mutex2);Console.WriteLine("线程1现在拥有了 mutex2");}static void ThreadProc2(){mutex2.WaitOne();Console.WriteLine("线程2获得了 mutex2");// 释放 mutex2 并等待获取 mutex1Mutex.SignalAndWait(mutex2, mutex1);Console.WriteLine("线程2现在拥有了 mutex1");}
}

 Close

关闭此Mutex实例持有的所有资源。

using System;
using System.Threading;class Program
{static void Main(){Mutex mutex = new Mutex(false, "MyMutex");// 使用 Mutex 进行一些操作// ...// 显式关闭 Mutexmutex.Close();}
}

具体例子:

在使用Mutex之后,后面关闭它,此时线程试图获取它或者做任何操作将会报错。

using System;
using System.Threading;class Program
{static void Main(){// 创建两个 Mutex 对象Mutex mutex1 = new Mutex();Mutex mutex2 = new Mutex();// 线程1尝试获得第一个 MutexThread thread1 = new Thread(() =>{Console.WriteLine("线程1 尝试获取 Mutex1...");mutex1.WaitOne();Console.WriteLine("线程1 已获取 Mutex1。");Thread.Sleep(2000); // 模拟一些工作Console.WriteLine("线程1 释放 Mutex1。");mutex1.ReleaseMutex();});// 线程2尝试获得第二个 MutexThread thread2 = new Thread(() =>{Console.WriteLine("线程2 尝试获取 Mutex2...");mutex2.WaitOne();Console.WriteLine("线程2 已获取 Mutex2。");Thread.Sleep(1000); // 模拟一些工作Console.WriteLine("线程2 释放 Mutex2。");mutex2.ReleaseMutex();});thread1.Start();thread2.Start();thread1.Join();thread2.Join();// 关闭 Mutex 对象mutex1.Close();mutex2.Close();Console.WriteLine("Mutex 对象已关闭。");}
}

此方法使用后,已经具有Mutex的代码不会停止运行,将会继续执行操作,只是不能再获得它。

到这你已经学会了怎么使用三种方法实现同步,可以的话点个关注支持一下,谢谢你奥。

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

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

相关文章

编程辅助工具下一个热门应用场景是什么?(一)

&#x1f381;&#x1f449;点击进入文心快码 Baidu Comate 官网&#xff0c;体验智能编码之旅&#xff0c;还有超多福利&#xff01;&#x1f381; 本系列视频来自百度工程效能部的前端研发经理杨经纬&#xff0c;她在由开源中国主办的“AI编程革新研发效能”OSC源创会杭州站1…

QT之QML学习五:添加自定义Qml组件,以及组件管理

开发环境: 1、Qt 6.7.2 2、Pyside6 3、Python 3.11.4 4、Windows 10 重要的事情说三遍,使用自定义qml参考链接: Qt官网参考网址!!! 重要的事情说三遍,使用自定义qml参考链接: Qt官网参考网址!!! 重要的事情说三遍,使用自定义qml参考链接: Qt官网参考网址!!!…

6.1 溪降技术:绳结

目录 6.1 绳结电子书&#xff1a;绳结1级概览正确打结打绳结绳结组成部分学习术语八字套结&#xff08;双八字结&#xff09;观看技术步骤双重单结&#xff08;反手结绳耳&#xff09;观看技术步骤骡子结&#xff08;驮马结&#xff09;观看技术步骤 6.1 绳结 电子书&#xff1…

POI生成Excel文件增加数据验证(下拉序列)

POI版本为5.2.2 正常的如果不超过255字符的数据验证可以参照如下代码&#xff1a; /*** <p>设置某列的数据验证</p>* param Sheet 作用于哪一个sheet* param colIndex 需要增加数据验证的列的索引* String[] names 数据验证的序列&#xff0c;就是excel下拉列表的内…

持续集成与持续交付CI/CD

CI/CD 是指持续集成&#xff08;Continuous Integration&#xff09;和持续部署&#xff08;Continuous Deployment&#xff09;或持续交付&#xff08;Continuous Delivery&#xff09; 持续集成&#xff08;Continuous Integration&#xff09; 持续集成是一种软件开发实践&…

HTML贪吃蛇游戏

文章目录 贪吃蛇游戏 运行效果代码 贪吃蛇游戏 贪吃蛇是一款经典的休闲益智游戏。本文将通过HTML5和JavaScript详细解析如何实现一个简易版的贪吃蛇游戏。游戏的主要逻辑包括蛇的移动、碰撞检测、食物生成等功能。以下是游戏的完整代码及注释解析。&#xff08;纯属好玩&#…

(学习总结17)C++继承

C继承 一、继承的概念与定义继承的概念继承定义1. 定义格式2. 继承基类成员访问方式的变化 继承类模板 二、基类和派生类间的转换三、继承中的作用域隐藏规则 四、派生类的默认成员函数4个常见默认成员函数实现一个不能被继承的类 五、继承与友元六、继承与静态成员七、多继承及…

HTML 揭秘:HTML 编码快速入门

HTML 揭秘&#xff1a;HTML 编码快速入门 一 . 前端知识介绍二 . HTML 介绍三 . HTML 快速入门四 . HTML 编辑器 - VSCode4.1 插件安装4.2 修改主题配色4.3 修改快捷键4.4 设置自动保存4.5 创建 HTML 文件4.5 书写 HTML 代码4.6 常见快捷键 五 . 基础标签5.1 字体标签5.1.1 col…

物品识别——基于python语言

目录 1.物品识别 2.模型介绍 3.文件框架 4.代码示例 4.1 camera.py 4.2 interaction.py 4.3 object_detection.py 4.4 main.py 4.5 运行结果 5.总结 1.物品识别 该项目使用Python&#xff0c;OpenCV进行图像捕捉&#xff0c;进行物品识别。我们将使用YOLO&#xff08…

大数据处理技术:HBase的安装与基本操作

目录 1 实验名称 2 实验目的 3 实验内容 4 实验原理 5 实验过程或源代码 5.1 Hbase数据库的安装 5.2 创建表 5.3 添加数据、删除数据、删除表 5.4 使用Java操作HBase 6 实验结果 6.1 Hbase数据库的安装 6.2 创建表 6.3 添加数据、删除数据、删除表 6.4 使用Java操…

【Elasticsearch系列七】索引 crud

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

数据结构(Day13)

一、学习内容 内存空间划分 1、一个进程启动后&#xff0c;计算机会给该进程分配4G的虚拟内存 2、其中0G-3G是用户空间【程序员写代码操作部分】【应用层】 3、3G-4G是内核空间【与底层驱动有关】 4、所有进程共享3G-4G的内核空间&#xff0c;每个进程独立拥有0G-3G的用户空间 …

[数据集][目标检测]烟叶病害检测数据集VOC+YOLO格式612张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;612 标注数量(xml文件个数)&#xff1a;612 标注数量(txt文件个数)&#xff1a;612 标注类别…

【鸿蒙】HarmonyOS NEXT星河入门到实战7-ArkTS语法进阶

目录 1、Class类 1.1 Class类 实例属性 1.2 Class类 构造函数 1.3 Class类 定义方法 1.4 静态属性 和 静态方法 1.5 继承 extends 和 super 关键字 1.6 instanceof 检测是否实例 1.7.修饰符(readonly、private、protected 、public) 1.7.1 readonly 1.7.2 Private …

汽车免拆诊断案例 | 沃尔沃V40 1.9TD断续工作

故障现象 一辆04款的沃尔沃V40 1.9 TD&#xff0c;发动机代码D4192T3&#xff0c;使用博世EDC15C发动机管理。客户说车子断续工作&#xff0c;怀疑是正时皮带出现问题。卸下上皮带盖&#xff0c;检查发现皮带仍然在原来的位置上并且没有出现松动。起动发动机&#xff0c;车辆能…

安卓玩机工具-----ADB与 FASTBOOT模式 图形化 多功能玩机刷机工具

工具说明 这款工具是英文版。易于使用的工具提供了用于运行 ADB 和 Fastboot 命令的图形用户界面。ADB 功能包括旁加载、安装和卸载应用程序、测试设备以及重新启动到不同的模式。可以使用 fastboot 命令进行设备管理;其中包括检查 Antirollback 和 active slots 等变…

YOLOv8 人体姿态估计动作识别关键点检测(代码+教程)

YOLOv8 人体姿态判断 项目介绍 YOLOv8 人体姿态判断 是一个基于最新YOLOv8模型的深度学习项目&#xff0c;旨在识别和分析人体姿态。该项目利用先进的计算机视觉技术和深度学习框架&#xff0c;通过摄像头捕捉实时图像或处理存储图像&#xff0c;识别人体的关键点&#xff0c…

在线查看 Android 系统源代码 AOSPXRef and AndroidXRef

在线查看 Android 系统源代码 AOSPXRef and AndroidXRef 1. AOSPXRef1.1. http://aospxref.com/android-14.0.0_r2/1.2. build/envsetup.sh 2. AndroidXRef2.1. http://androidxref.com/9.0.0_r3/2.2. build/envsetup.sh 3. HELLO AndroidReferences 1. AOSPXRef http://aospx…

加密与安全_优雅存储用户密码的最佳实践

文章目录 Pre概述最佳实践避免使用MD5、SHA1等快速哈希算法加盐哈希 &#xff08;不推荐&#xff09;使用BCrypt、Argon2等慢哈希算法 (推荐)BCrypt Code1. 自动生成和嵌入盐2. 哈希结果的格式3. 代价因子 BCrypt特点 防止暴力破解1. 登录失败锁定2. 双因素认证&#xff08;2FA…

Golang | Leetcode Golang题解之第409题最长回文串

题目&#xff1a; 题解&#xff1a; func longestPalindrome(s string) int {mp : map[byte]int{}for i : 0; i < len(s); i {mp[s[i]]}res : 0for _, v : range mp {if v&1 1 {res v - 1} else {res v}}if res<len(s) {res}return res }