C# 篇基础知识10——多线程

1.线程的概念

单核CPU的计算机中,一个时刻只能执行一条指令,操作系统以“时间片轮转”的方式实现多个程序“同时”运行。操作系统以进程(Process)的方式运行应用程序,进程不但包括应用程序的指令流,也包括运行程序所需的内存、寄存器等资源。因为交替时间很短(一般只有几十毫秒),人们根本感觉不到如此短暂的停顿,所以在表面上看来就像多个工作同时进行似的。因此进程在宏观上是并发进行的,在微观上是交替进行的。

后来出现了多线程技术(Multi-threading),可以通过在一个进程中创建多个线程(Threading),系统以“时间片轮转”的方式交替执行多个线程,使得可以在一个程序中同时执行多项工作。同一个进程中的所有线程共享进程的资源,所以它们之间的切换就比进程间的切换快的多,因此线程可以看作轻量级进程(Lightweight Process)。现代的操作系统都是多进程(Multi-process)的操作系统,每个进程中运行一个或多个线程,所以大多数时间操作系统中都有多个线程并发运行。操作系统中有专门的调度程序管理线程,它根据事先设计好的算法轮流执行每个线程。线程是操作系统进行CPU 调度的基本单位,线程的调度是由操作系统自动完成的,无须程序员关心。程序员只需编写好线程即可,线程的轮转交由操作系统完成。随着多核心CPU的出现,使得线程能够真正的实现同步执行,多线程技术从此翻开新的篇章。

2.Thread类

一般情况下,每开启一个应用程序,系统就会创建一个与该程序相关的的进程,紧接着进程就会创建一个主线程(Main Thread),然后从主函数中的代码开始执行。可以在一个应用程序中创建任意多个线程,每个线程完成一项任务。C#中,线程由System.Threading 命名空间中的Thread 类实现,声明语句:

Thread workThread = new Thread(entryPoint);

其中entryPoint 代表一个入口方法,线程的具体代码放在入口方法中,系统从入口方法的第一句代码开始执行线程。入口方法的参数和返回值类型由ThreadStart 委托或ParameterizedThreadStart 委托规定。

public delegate void ThresdStart();

public delegate void ParameterizedThreadStart(Object obj);

除了通过委托传递线程的入口方法外,还可以通过匿名方法或Lambda表达式创建线程。

Thread drawGraphThread=new Thread(delegate() { //入口方法中的代码});

Thread drawGraphThread=new Thread(() => { //入口方法中的代码});

匿名方法可以使用外部变量,所以用匿名方法定义的线程可以使用在线程前面定义的变量,这弥补了入口方法没有参数和返回值的问题。

3.线程的优先级

计算机中经常会有多个任务同时运行,其中总有一些看起来更紧急,更需要优先执行。线程的优先级可以通过Thread类Priority属性设置,Priority属性是一个ThreadPriority型枚举,包含5个优先等级:Highest、AboveNormal、Normal BelowNormal、Lowest。应先设置线程优先级,再执行线程,并且,任何一个程序的Main()方法将占用一个主线程。

//改变线程优先级

threadA.Priority = ThreadPriority.AboveNormal;

threadB.Priority = ThreadPriority.BelowNormal;

//启动线程

threadA.Start();

threadB.Start();

4.线程的插入

Thread类的Join()方法能够将两个原本交替执行的线程变为顺序执行。

Using System.Threading;

Static void Main(string [] args)

{ //线程A

Thread threadA=new Thread(delegate()

  {  for(int i=0;i<=10000000;i++)

    {   if(i%1000000==0)

        {Console.Write(‘A’); }

     }

});

//线程B

Thread threadB=new Thread(delegate()

{  for (int i=0;i<=50000000;i++)

{ if(i%1000000==0)

        {Console.Write(‘B’);}

    }

   //在这里插入线程A

   threadA.Join();

   for(int i=0;i<=50000000;i++)

   {  if(i%10000000==0)

      { Console.Write(‘b’);}

   }

 });

//启动线程

threadA.Start();

threadB.Start();

}

一开始两个线程交替进行,当线程 B 执行到语句“threadA.Join()”时,线程A 中剩余的代码插入到线程B 之中,从此刻起,停止执行线程B,专门执行线程A,直到执行完线程A 中的所有语句,才去执行线程B 中剩余的语句。从线程 B 的角度看,在线程B 中调用threadA.Join(),相当于在在线程B 中调用了一个方法,只有线程threadA 执行完毕之后该方法才会返回,Join()方法还可以接受一个表示毫秒数的参数,当达到指定时间后,即使线程A 还没运行完毕, Join()方法也返回,这时线程A 和线程B 再次处于交替运行中。

5.线程的状态

线程的状态由Thread类的ThreadState属性表示:

 

当一个线程被创建后,它就处于Unstarted 状态,直到调用了Start()方法为止。但处于Running 状态的线程也不是一定正在被CPU 执行,可能该线程的时间片刚刚用完,CPU 正在处理其他线程,过一段时间后才会处理它。

有三种方法使线程由 Running 状态变为WaitSleepJoin 状态。第一种情况是为了保持线程间的同步而使之处于等待状态,这一点将在下一节讲到。第二种情况是线程调用了Sleep()方法而处于睡眠状态,当达到指定的睡眠时间以后,线程将会回到Running 状态。第三种情况是调用了Join()方法,比如在线程A 的代码中调用了线程B.Join()方法,线程A 将处于WaitSleepJoin 状态,直到线程B 结束,开始继续执行线程A 时为止。如果当线程处于 Running 状态时调用线程的Suspend()方法,线程将由Running 状态变为SuspendedRequested 状态(请求挂起状态),线程一般会再继续执行几个指令,当确保线程在安全的状态下时,挂起线程,这时线程变为Suspended 状态(挂起状态)。调用线程的Resume()方法,可使线程回到Running 状态。

线程的状态是由操作系统的调度程序决定的,所以除了在一些调试方案中,一般不使用线程的状态。但线程的Background 状态除外,可以通过Thread 类的IsBackground 属性把线程设置为Background 状态。其实后台线程跟前台线程只有一个区别,那就是后台线程不妨碍程序的终止。一旦一个应用程序的所有前台线程都终止后,CLR 就通过调用任意一个存活中的后台线程的Abort()方法来彻底终止应用程序。另外 Thread 类还有一个IsAlive 属性,这是个只读属性,用来说明线程已经启动,还没有结束。

6.线程的同步 

1)线程同步的概念

同上运行的线程,有的相互间没有任何联系,称为无关线程,而有些线程之间则是有联系的,例如一个线程等待另一个线程的运算结果,两个线程共享一个资源等,这种线程称为相关线程。例如在网上观看在线视频,一个线程下载视频,另一个线程播放视频,两个线程相互合作,才能得到较佳的观看体验。线程的相关性集中体现在对同一资源的访问上,把这种多个线程共享的资源称为临界资源,它可以是内存中的一个变量,也可以是一个文件,也可以是一台打印机等。

系统中往往有多个线程交替执行,它们被执行的时间是不确定,当需要两个线程精确协同工作才能共同完成好一项任务的情况称为线程同步(Synchronization)。如何保证两个线程同步?.NET框架提供了一系列的同步类,最常用的包括Interlocked(互锁)、Monitor(管程)和Mutex(互斥体)。

(2)互锁(Interlocked类)

通过Interlocked类来控制线程的同步,为多个线程共享的变量提供原子操作,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束。例如:

using System.Threading;

class Program

{  private static char buffer; //缓冲区,只能容纳一个字符

//标识量(缓冲区中已使用的空间,初始值为0 )

private static long numberOfUsedSpace = 0;

static void Main(string[] args)

{ Thread writer = new Thread(delegate()

{

string sentence = "无可奈何花落去,似曾相识燕归来,小园香径独徘徊。";

for(int i = 0; i < 24; i++)

{

//写入数据前检查缓冲区是否已满

while(Interlocked.Read(ref numberOfUsedSpace) == 1)

{Thread.Sleep(10);}

buffer = sentence[i]; //向缓冲区写入数据

Interlocked.Increment(ref numberOfUsedSpace);

}

});

Thread reader = new Thread(delegate()

{for(int i = 0; i < 24; i++)

{//读取数据前检查缓冲区是否为空

while(Interlocked.Read(ref numberOfUsedSpace) == 0)

{Thread.Sleep(10);}

char ch = buffer;

Console.Write(ch);

//读取数据后把缓冲区标记为空(由1 变为0)

Interlocked.Decrement(ref numberOfUsedSpace);

}

});

//启动线程

writer .Start();

reader.Start(); }

}

3Monitor

另一种同步方法使用Monitor类,它使用独占锁的方式控制线程同步,只有获得独占锁的线程才能访问临界资源。当一个线程进入临界区时,首先调用Monitor 类的Enter()方法,尝试获取临界资源的独占锁,若独占锁已被其他线程占用,就进入等待状态,睡眠在临界资源上,直到独占锁没有被其他线程占用,该线程就会获取独占锁,执行操作临界资源的代码。Monitor 会纪录所有睡眠在临界资源上的线程,当线程退出临界区时,需要通过调用Monitor 类的Pulse()方法唤醒睡眠在临界资源的线程。Monitor 类的部分方法如下表所示:

using System.Threading;

class Program

{private static char buffer;

//用于同步的对象(独占锁)

private static object lockForBuffer = new object();

static void Main(string[] args)

{Thread writer = new Thread(delegate()

{ string sentence = "无可奈何花落去,似曾相识燕归来,小园香径独徘徊。";

for (int i = 0; i < 24; i++)

{  lock(lockForBuffer)

{ buffer = sentence[i];

Monitor.Pulse(lockForBuffer); //唤醒睡眠在此临界资源上的其它线程

Monitor.Wait(lockForBuffer); //让当前线程睡眠在临界资源上 }

}

});

Thread reader = new Thread(delegate()

{ for (int i = 0; i < 24; i++)

{ lock (lockForBuffer)

{ char ch = buffer;

Console.Write(ch);

Monitor.Pulse(lockForBuffer); //唤醒睡眠在临界资源上的线程

Monitor.Wait(lockForBuffer); //让当前线程睡眠在临界资源上}

}

});

//启动线程

writer .Start();

reader.Start();

} }

实际上,旧版的C#中其编码方式如下左图,新版的C#对其进行了简化,设计了lock(object o){}的方式。

 

在线程 writer 中,因为当缓冲区中的数据被读走以后还要继续向缓冲区中写数据,所以当写完数据后调用了Monitor.Wait()方法,让线程writer 睡眠在临界资源上,直到线程reader 读完数据后把它唤醒。出于同样的理由,当线程reader 读完数据后,也调用了Monitor.Wait()方法。为了确保退出临界区时临界资源得到释放,使用 Monitor 类的代码应该放在try 语句中,并在finally 块中调用Monitor.Exit()方法。而简化版的lock 语句,执行完毕后会自动执行Monitor.Exit()方法,释放临界资源,二者功能完全等价。

需要注意的是,Monitor 类只能锁定引用类型对象。

当一个线程以独占锁的方式访问资源时,其他线程就不能访问该资源,只有lock 语句结束后其他线程才能访问。lock 语句的相当于临时禁用了应用程序的多线程功能。一般情况下,当有多个线程对同一个资源进行写操作时,就应当进行同步操作。但是如果一个线程在某资源上放置了一把锁,其他访问该资源的线程就只能暂停,使程序的效率大打折扣。所以只有必要的时候才设置独占锁。

4)互斥体(Mutex类)

在操作系统中,许多线程常常需要共享资源,而这些资源往往要求排他性的使用,即一次只能为一个线程服务。比如打印机一次只能打印一个文档,一个文件一次只允许一个线程写入数据等。这种排他性地使用共享资源称为线程间的互斥(Mutual Exclusion)。线程互斥实质上也是同步,可以看做一种特殊的线程同步。线程的互斥常用Mutex 类(互斥体)实现,利用它可以对资源进行独占性访问。与Monitor 类相似,只有获取Mutex 对象的所属权的线程才能进入临界区,未获得Mutex 对象所属权的线程只能在临界区外等待。使用Mutex 类要比使用Monitor 类消耗更多的系统资源,但它可以跨越应用程序边界,在多个应用程序之间进行同步。Mutex 类的部分方法如下表所示:

 

 互斥体有两种类型:局部互斥体和系统互斥体。局部互斥体只能在创建它的程序中使用,而系统互斥体则能被系统中不同的应用程序共享。创建系统互斥体,只需在构造函数中为互斥体对象起一个“系统名称”即可。 

操作系统根据互斥体的系统名称辨别互斥体,不管互斥体对象创建于哪个应用程序中,只要具有相同的系统名称,就被认为是同一个系统互斥体。下面分别创建两个程序,它们都每隔一秒钟向文件 TimeRecord.txt 中写入一条包含当前系统时间的记录。显然,为了保证每条记录的完整性,当一个程序向文件中写入记录时,另一个程序必须等待。因此需要用Mutex 进行同步。

static void Main(string[] args)

{ Thread threadA=new Thread(delegate()

{ Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");

string fileName = @"D:\ TimeRecord.txt";

for (int i = 1; i <= 10; i++)

{try{

//请求互斥体的所属权,若成功,则进入临界区,若不成功,则等待

fileMutex.WaitOne();

//在临界区中操作临界资源,即向文件中写入数据

File.AppendAllText(fileName, "threadA: " + DateTime.Now + "\r\n");

}

catch (System.Threading.ThreadInterruptedException)

{Console.WriteLine("线程A 被中断。");}

finally

{fileMutex.ReleaseMutex(); //释放互斥体的所属权}

Thread.Sleep(1000);

}

});

threadA.Start();

}

//创建第二个程序"MutecB",在主函数中输入下面的代码

static void Main(string[] args)

{

Thread threadB = new Thread(delegate()

{//创建互斥体

Mutex fileMutex = new Mutex(false, "MutexForTimeRecordFile");

string fileName = @"D:\ TimeRecord.txt";

for (int i = 1; i <= 10; i++)

{

try

{//请求互斥体的所属权,若成功,则进入临界区,若不成功,则等待

fileMutex.WaitOne();

//在临界区中操作临界资源,即向文件中写入数据

File.AppendAllText(fileName, "threadB: " + DateTime.Now + "\r\n");

}

catch (System.Threading.ThreadInterruptedException)

{Console.WriteLine("线程B 被中断。");}

finally

{fileMutex.ReleaseMutex(); //释放互斥体的所属权}

Thread.Sleep(1000);

}

});

threadB.Start();

System.Diagnostics.Process.Start("MutexA.exe"); //启动程序MutexA.exe

}

上面两个程序中,我们分别创建了一个互斥体对象,因为它们的系统名称都是“MutexForTimeRecordFile”,所以操作系统认为它们是同一个Mutex 对象,从而实现两个应用程序互斥地访问同一个文件。用【生成】菜单中的命令分别生成程序 MutexA.exe 和程序MutexB.exe,然后把它们复制到同一个文件夹中。双击程序 MutexB.exe,因为程序MutexB.exe 中包含有启动程序MutexA.exe 的代码,所以两个程序都被启动。两个程序运行完毕后,打开TimeRecord.txt 文件,观察结果:两个程序实现了交替的向同一文件中写入时间记录。当一个程序向文件中写入文件记录时,另一个程序只能处于等待状态,从而保证了每条记录的完整性。

5.死锁

多个线程间的同步如果设计不当,就会造成死锁(Deadlock)。死锁是指多个线程共享某些资源时,都占用一部分资源,而且都在等待对方释放另一部分资源,从而导致程序停滞不前的情况。

下面的程序演示了一种典型的死锁情形。一对情侣共吃一份西餐,并且共用一副刀叉。只有同时获得刀子和叉子时,才可以吃东西,吃完以后就放下刀叉,供对方使用。如果刀子或叉子正好被对方拿起,就只能等待,直到对方放下为止。

class Program

{ private static object knife = new object(); //临界资源:刀子

private static object fork = new object(); //临界资源:叉子

static void Main(string[] args)

{//线程:女孩的行为

Thread girlThread = new Thread(delegate()

{//女孩和男孩聊天

Console.WriteLine("今天的月亮好美啊~~~");

//过了一会儿,女孩饿了,就去拿刀子和叉子

lock (knife)

{ GetKnife();

//*(待会儿会在这里添加一条语句)

lock (fork)

{ GetFork();

Eat(); //同时拿到刀子和叉子后开始吃东西

Console.WriteLine("女孩放下叉子");

Monitor.Pulse(fork); }

Console.WriteLine("女孩放下刀子");

Monitor.Pulse(knife); }

});

girlThread.Name = "女孩"; //定义线程的名称

//线程:男孩的行为

Thread boyThread = new Thread(delegate()

{ //男孩和女孩聊天

Console.WriteLine("\n 你更美!");

lock (fork)

{ GetFork();

lock (knife)

{ GetKnife();

Eat(); //同时拿到刀子和叉子后开始吃东西

Console.WriteLine("男孩放下刀子");

Monitor.Pulse(knife); }

Console.WriteLine("男孩放下叉子");

Monitor.Pulse(fork);

}

});

boyThread.Name = "男孩"; //定义线程的名称

//启动线程

girlThread .Start();

boyThread.Start();

}

//方法:拿起刀子

static void GetKnife()

{Console.WriteLine(Thread.CurrentThread.Name + "拿起刀子。");}

//方法:拿起叉子

static void GetFork()

{Console.WriteLine(Thread.CurrentThread.Name + "拿起叉子。");}

//方法:吃东西

static void Eat()

{Console.WriteLine(Thread.CurrentThread.Name + "吃东西。");}

}

一般情况下,程序可以正常运行,结果如下:

但在某些特殊情况下就会出现死锁现象。假设女孩刚好拿起了刀子,正要拿叉子时,操作系统把线程切换到了男孩,这时男孩也想吃饭,于是拿起了叉子,但随即发现刀子已被女孩占有,所以男孩线程进入睡眠状态,等待女孩释放刀子。过一会儿,线程再次切换到女孩,当女孩试图拿起叉子时发现叉子已被男孩占有,于是女孩线程也进入睡眠状态,等待男孩释放叉子。最终男孩等女孩释放刀子,女孩等男孩释放叉子,双方都在无休止的等待对方,进入了死锁状态。

出现死锁的前提条件是两个线程出现交替,交替过程中各占有一部分资源(而每个线程运行都需要获得整个资源)由于例子中的两个线程都很小,多数情况下都能在一个时间片内完成,所以出现死锁的概率还是很小的。线程运行时间的越长,出现交替的情况越多,出现死锁的概率越大。

死锁会造成程序停滞不前,所以我们在编写多线程程序时一定要注意避免死锁现象的发生。其实上面的问题很好解决,只要两个线程以相同的顺序访问临界资源即可。

7.线程池

一般情况下我们都使用Thread类创建线程,因为通过Thread对象可以对线程进行灵活的控制。但创建线程和销毁线程代价不菲,过多的线程会消耗掉大量的内存和CPU资源,假如某段时间内突然爆发了100 个短小的线程,创建和销毁这些线程就会消耗很多时间,可能比线程本身运行的时间还长。为了改善这种状况,.NET提供了一种称之为线程池Thread Pool)的技术。线程池提供若干个固定线程轮流为大量的任务服务,比如用10 个线程轮流执行100 个任务,当一个线程完成任务时,并不马上销毁,而是接手另一个任务,从而减少创建和销毁线程的消耗。线程池由System.Threading 命名空间中的ThreadPool 类实现,其部分方法如下表所示:

ThreadPool 是一个静态类,不必创建实例就可以使用它。一个应用程序最多只有一个线程池,它会在首次向线程池中排入工作函数时自动创建。

namespace ThreadPoolTest

{

class Program

{

public delegate void WaitCallback(Object dataForFunction);

public static void ThreadPoolTest()

{//向线程池中添加100个工作线程

for (int i = 1; i <= 100; i++)

{ThreadPool.QueueUserWorkItem(new WaitCallback(WorkFunction), i);}

}

//工作函数

public static void WorkFunction(object n)

{ Console.Write(n + "\t");}

static void Main(string[] args)

{ThreadPoolTest();

Console.ReadKey(); //按下任意键结束程序}

}}

结果如下图所示:

下面,研究一下线程池运行过程中线程数目的变化情况,从而加深对线程池的理解。为了叙述方便,假设下限为10,上限为30。

①当线程池被创建后,里面就会创建10 个空线程(和下限值相同)。

②当向线程池中排入一个任务后,就会有一个空线程接手该任务,然后运行起来。随着不断向线程池中排入任务,线程池中的空线程逐一运行起来。

③随任务不断增加,某一时刻任务数量会超出下限,这时线程数量不够用了,但线程池并不会立即创建新线程,而是等待500 毫秒左右,看看在这段时间是否有其它线程完成任务并接手这个请求,避免因创建新线程而造成的消耗。如果这段时间没有线程完成任务,就创建一个新线程去执行新任务。

④在任务数量超过下限后,随着新任务的不断排入,线程池中线程数量持续增加,直至达到上限值为止。

⑤当线程数量达到上限时,继续增加任务,线程数量将不再增加。多余的任务就线程池外排队等待。线程池某个线程完成任务后,就从等待队列中选择一个任务继续执行。

⑥随着任务逐步完成,线程池外部等候的任务被逐步调入线程池,任务的数量逐步减少,但线程的总数保持恒定,始终为30(和上限值相同)。

⑦随着任务的逐渐减少,某一时刻任务数量会小于上限值,这时线程池内多余的线程会在空闲2 分钟后被释放并回收相关资源。线程数目逐步减少,直到达到下限值。

⑧当任务数量减小到下限值之下时,线程池中的线程数目保持不变(始终和下限值相同),其中一部分在执行任务,另一部分处于空运行状态。

⑨当所有任务都完成后,线程池恢复初始状态,运行10 个空线程。

由上面的论述可以看出线程池提高效率的关键是一个线程完成任务后可以继续为其他任务服务,这样就可以使用有限的几个固定线程轮流为大量的任务服务,从而减少了因频繁创建和销毁线程所造成的消耗。ThreadPool 中的线程不用手动开始,也不能手动取消,你要做的只是把工作函数排入线程池,剩下的工作将由系统自动完成。如果想对线程进行更多的控制,那么就不适合使用线程池。在以下情况中不宜使用ThreadPool类而应该使用单独的Thread 类:

①线程执行需要很长时间(如果有些线程长期占用线程池,那么对在外面排队的任务说就是灾难);

②需要为线程指定详细的优先级;

③在执行过程中需要对线程进行操作,比如睡眠,挂起等。

所以 ThreadPool 适合于并发运行若干个运行时间不长且互不干扰的函数。 

 

转载于:https://www.cnblogs.com/Sweepingmonk/p/10868056.html

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

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

相关文章

快速理解binary cross entropy 二元交叉熵

Binary cross entropy 二元交叉熵是二分类问题中常用的一个Loss损失函数&#xff0c;在常见的机器学习模块中都有实现。本文就二元交叉熵这个损失函数的原理&#xff0c;简单地进行解释。 首先是二元交叉熵的公式 : Loss−1N∑i1Nyi⋅log⁡(p(yi))(1−yi)⋅log(1−p(yi))Loss …

Docker搭建自己的GitLab

Docker搭建自己的GitLab docker 介绍 **GitLab: ** GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的web服务 **Docker: ** Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖…

flowable 任务节点多实例使用

我们在使用Flowable 工作流引擎的时候&#xff0c;最常用的肯定是任务节点&#xff0c;因为在OA系统、审批系统、办公自动化系统中核心的处理就是流程的运转&#xff0c;在流程运转的时候&#xff0c;可能我们有这样的一个需求&#xff0c;在一个任务节点的时候&#xff0c;我们…

Linux的目录结构

Linux文件系统是呈树形结构&#xff0c;了解Linux文件系统的目录结构&#xff0c;对于我们驾驭Linux还是有必要的。 目录 说明 / Linux文件系统的入口&#xff0c;也是处于最高一级的目录 /bin 基本系统所需要的命令。功能和/usr/bin类似&#xff0c;这个目录中的文件都是…

一文看懂卷积神经网络CNN的核心

在之前&#xff0c;我总结了关于计算机神经网络与梯度下降的核心&#xff0c;详见下文链接 : 一文看懂计算机神经网络与梯度下降 本文主要会对图像相关的机器学习中最为重要的网络&#xff0c;卷积神经网络作个人的理解分析。 1. 为什么要使用卷积神经网络 在讲述原理之前&am…

[LeetCode] Two Sum

一刷&#xff1a; import java.util.Arrays;public class Solution1 { public int[] twoSum(int[] nums, int target) {int[] indexnew int[2];int sum0;for (int i 0; i < nums.length; i) {for (int j i1; j < nums.length; j) {sumnums[i]nums[j];index[0] i;index[…

机器学习理论梳理2 : KNN K近邻分类模型

本文主要梳理KNN&#xff0c;K近邻模型的基本原理。 从机器学习的大分类来看&#xff0c;K近邻模型属于监督学习中的一种判别式模型&#xff0c;常用于分类问题。初始的数据集中&#xff0c;包含了已经分类标签好的数据。一句话来说&#xff0c;K近邻模型就是通过计算实例与现…

docker安装配置gitlab详细过程

1、方法一 1 docker pull beginor/gitlab-ce:11.0.1-ce.0 2、方法二 如果服务器网路不好或者pull不下来镜像&#xff0c;只能在其它网路比较好的机器上pull下来镜像&#xff0c;导出成一个文件&#xff0c; 再下载上传到网路不好的机器上&#xff0c;然后再从文件中导出来&am…

集合对偶律:分别用图文证明

集合几个法则&#xff1a; 求证&#xff1a; 注&#xff1a;右上角C表示此集合的补集/余集 语言描述&#xff1a;A 并 B的补集 A的补集 交 B的补集 A交B的补集 A的补集 并 B的补集 文字证明&#xff1a;&#xff08;思路&#xff1a;证明两个集合相等&#xff0c;可证两集合…

keras实现嘴唇图像autoencoder

本文分享了我在silent speech 项目过程中实现的基于嘴唇图像数据集的autoencoder自编码器。输入输出都是64∗6464*6464∗64的嘴唇灰度图。自编码器由编码解码两个部分构成&#xff0c;同时实现了利用checkpoint在每个epoch运算时&#xff0c;自动保存测试集loss更小的模型。 数…

historyReverser array reverse

historyReverser & array reverse "use strict";/**** author xgqfrms* license MIT* copyright xgqfrms** description historyReverser* augments Reverse 逆向 / Recursive 递归* example* link**/const historyReverser (datas [], text , debug false)…

pip国内加载速度慢解决方法

在国内使用pip安装包时有时会发现安装速度非常慢&#xff0c;甚至连接不上源。 为了加快pip的下载速度&#xff0c;我们可以主动使用 -i命令来切换到国内源。 下面放出实测好用的国内源 : 清华&#xff1a;https://pypi.tuna.tsinghua.edu.cn/simple 阿里云&#xff1a;http:…

oracle--导出、导入blob类型的字段

oracle--导出、导入blob类型的字段 blob是oracle中的一个数据类型&#xff0c;保存的是压缩后的二进制形式的大数据。 数据迁移如果涉及到blob字段&#xff0c;都不好处理&#xff0c;因为无法用常规方法进行操作&#xff0c;如&#xff1a;使用select查看该字段&#xff0c;…

深度学习分布式训练小结

分布式训练本质上是为了加快模型的训练速度&#xff0c;面对较为复杂的深度学习模型以及大量的数据。单机单GPU很难在有限的时间内达成模型的收敛。这时候就需要用到分布式训练。 分布式训练又分为模型并行和数据并行两大类。 1. 数据并行 数据并行在于将不同batch的数据分别…

MAC配置JCO,与找不到sapjco3异常

①到jco官网下载jco压缩包&#xff0c;解压 ②把libsapjco3.jnilib 放到一个文件夹中 把该路径配置到环境变量中 ③项目运行有可能会出现异常&#xff1a;找不到 sapjco3 &#xff1b; 第一种解决方式&#xff1a;配置虚拟机参数&#xff1a;-Djava.library.path之前环境变量路…

Java高并发之BlockingQueue

前言碎语 当系统流量负载比较高时&#xff0c;业务日志的写入操作也要纳入系统性能考量之内&#xff0c;如若处理不当&#xff0c;将影响系统的正常业务操作&#xff0c;之前写过一篇《spring boot通过MQ消费log4j2的日志》的博文&#xff0c;采用了RabbitMQ消息中间件来存储抗…

IP通信基础回顾2(第三周)

1.TCP报文 序号字段占4个字节。TCP连接中传送的数据流中每一个字节都编上一个序号。序号字段的值则是本报文段所发送的数据第一个字节的序号。 确认序号占4个字节。是期望收到的对方的下一个报文段字节胡序号。首部长度占4个字节。指出TCP首部长度在20-60字节之间&#xff0c;所…

ThreadPoolExecutor线程池 + Queue队列

1&#xff1a;BlockingQueue继承关系 java.util.concurrent 包里的 BlockingQueue是一个接口&#xff0c; 继承Queue接口&#xff0c;Queue接口继承 Collection BlockingQueue----->Queue-->Collection 图&#xff1a; 队列的特点是&#xff1a;先进先出&#xff08;FIFO…

linux基础文件管理软硬链接

一、文件系统的基本结构 1、文件和目录被组成一个单根倒置树目录结构 2、文件系统从根目录下开始&#xff0c;用“/”表示 3、根文件系统&#xff08;rootfs&#xff09;&#xff1a;root filesystem文件名区分大小写 4、以 . 开头的文件为隐藏文件 5、路径用/隔离 6文件有两类…

mybatis动态更新xml文件后热部署,不重启应用的方法

mybatis应用程序&#xff0c;由于是半自动化的sql, 有大量的sql是在xml文件中配置的&#xff0c;而在开发程序的过程中&#xff0c;通常需要边写sql变调试应用。但在默认情况下&#xff0c;xml文件里配置的sql语句是被放入到缓存中去了&#xff0c;每次更改有sql语句的xml文件&…