C++C#Thread多线程总结
1.背景
⼀直都想写⼀篇关于多线程的⽂章,总结⼀下这⽅⾯的知识,也为⾃⼰和有需要的⼈提供参考,不再赘述,开始吧,
现在计算机⼀般都是多核的,4核和8核的⽐较多件,⽤于线上计算或专⽤⼯作的计算机更是⼤概率“⼟豪”级别,如果不充分利⽤计算机的这⼀资源,那么将是⼀种浪费,尤其在现在⾏业竞争激烈,拼性能
的时代,充分利⽤有限的计算机资源显得尤为重要,试看⼀下你⽤visual studio编译项⽬时,⾃⼰电脑的cpu使⽤率会多⾼?没错,接近100%!你所⽤的开发⼯具都在充分利⽤多核资源,节省你开发编译时
的时间,当你在热修复⼀个问题时,当你在上线前的冲刺时,或者当你在平时的⼀次普通按F7键时,它都在尽量的⾼效率运⾏,尽量节省你的时间。作为开发者,你的⽤户也是如此,他们需要节省时间,
感受友好的软件使⽤体验,⾄少在特定的情况下,如果你的程序恰好提供这种服务,他们将⽐较舒缓,同时也更可能提升程序的竞争⼒。
c++和c#的⼀起总结下吧,正好也可以对⽐⼀下,加深相关知识的理解。
注意本⽂可能不会详尽的介绍所有线程相关的知识,但是会结合样例尽量充分的讲述多线程相关技术。
2.多线程技术
2.1.线程类
2.1.1.C++
c++11之后⼜thread类,同样⼀些第三⽅库中也有线程相关类的实现,如boost库,甚⾄还有⼀些第三⽅库实现了线程池(threadPool)类,我们这⾥只关注std中的thread类,
thread threadItem(work);
// other work there.
// Blocks until the associated thread completes.
threadItem.join();
2.1.2.C#
相对来说c#的 frameworks中的相关类就多了不少,毕竟c#写起来很流畅,这是有原因的~
创建专有线程,通过Thread类,
t = new Thread(this.Run);
t.Start();
由于线程的创建和销毁需要消耗⼀定的时间和内存,能不能尽量少的进⾏创建和销毁的过程,⽽⼜可以发挥多线程的优势?想更⾼效率的使⽤线程, 让线程持续的⼯作,做完⼀个任务,还会继续去执⾏另外的任务(如果有的话),那么可以使⽤线程池技
术,C#framework已经实现了相关类,
ThreadPool.QueueUserWorkItem(ThreadProc);
更⾼级的封装,将线程池的概念继续进⾏封装,使⽤task,⽤户只需要知道task执⾏是异步⽅式就可以了(会使⽤线程池技术来实现),另外获取task的结果也很⽅便,甚⾄还可以给task发信号让它终⽌执⾏!是不是很⽅便?⽤户⽆须⾃⼰写复杂的实现,只需要调⽤接⼝就能达到⽬的,具体请参考Microsoft⽂档或《CLR Via C#》。
using System;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
var t = Task<int>.Run(() =>
{
// Just loop.
int max = 1000000;
visual studio和vs code的区别int ctr = 0;
for (ctr = 0; ctr <= max; ctr++)
{
if (ctr == max / 2 && DateTime.Now.Hour <= 12)
{
ctr++;
break;
}
}
return ctr;
});
Console.WriteLine("Finished {0:N0} iterations.", t.Result);
}
}
// The example displays output like the following:
// Finished 1,000,001 loop iterations.
var t = Task<int>.Factory.StartNew(() =>
{
// Just loop.
int max = 1000000;
int ctr = 0;
for (ctr = 0; ctr <= max; ctr++)
{
if (ctr == max / 2 && DateTime.Now.Hour <= 12)
{
ctr++;
break;
}
}
return ctr;
});
Console.WriteLine("Finished {0:N0} iterations.", t.Result);
当然这些类中还有其他的⽅法,具体请参考MSDN⽂章(在VS中光标选中相应的类,按F1)、《CLR Via C#》(这本书很经典,⼤⽜著作,会详细⼜不冗余的讲述.Net FrameWorks中的机制原理及当初为什么这样设计),
已经有了这么⾼级的封装,还有其他的吗?有的,为了⽅便开发者使⽤多线程技术,充分的利⽤计算机的多核资
源,.NetFrameWorks库还实现了⼀些常⽤操作的异步版本,如Parallel.ForEach,
Parallel.ForEach
尽情探索吧,
2.2.互斥锁
互斥锁主要⽤在多线程需要争夺抢占资源(数据)的场景,对资源(数据)进⾏加锁,避免多线程同时读写可能发⽣的不明确⾏为,使逻辑简单且在逻辑上减少出错的可能性。
这个概念也对应【资源同步】,和【线程同步】是不⼀样的概念,下⽂会讲到。
如果两个线程之间不需要任务数据共享,那么它们两个可以⾃由⽽独⽴的运⾏,这时不需要线程锁;但实际编程中很多多线程使⽤的场景都需要共享⼀些数据,如缓存数据,线程a执⾏某个任务时将key和结果数据存到缓存中,其他线程在执⾏相同类型的任务时需要去访问缓存数据,看是否已经计算过,如果计算过那直接返回就可以了(避免重复的计算过程),如果没有计算过,那么执⾏计算过程并将结果数据也写到缓存中,这种很常见的场景就涉及到了资源抢占的问题。
⼀般来说,多线程环境中对⼀份数据的访问有⼏种规则:
读和写是相互排斥的,即如果有线程正在读,那么其他线程不能写;同样如果有线程在写,那么其他线程不能同时读;
写和写也是互斥的,即同时只能有⼀个线程在读(想想为什么git上,两个⼈同时修改了某个地⽅的代码,最后合并时会出现冲突);
但读和读是不排斥的,即同时可以有多个线程在读;
接来来我们来看c++/c#中锁的使⽤,
2.2.1.C++
c++中使⽤锁也有多种⽅式,除了标准库,⼀些第三⽅库也实现了锁,有些库实现的既好⽤⼜⾼效,⽽⾜以影响标准库的后续版本;我们这⾥只看c++17中的,
mutex,可使⽤unique_lock来获取锁,离开unique_lock作⽤域时,它会⾃动的释放锁,当然可以直接调⽤mutex的⽅法,还有其他的⽅式如std::lock_guard,这⾥不再介绍,读者可⾃⾏查阅相关资料。
bool GetOrCreateObject(...)
{
// in this func, you can read or write, and acquire object
unique_lock<mutex> uLcM(itemMutex);
// here you can do something
}
上述例⼦是将读和写封装在了⼀个⽅法中,因此只需要mutex即可;如果是将读和写放到了不同的⽅法中,那么下⽂中的
shared_mutex更适⽤。
shared_mutx,可使⽤unique_lock、shared_lock来获取和释放锁,相对于mutex,其适⽤⽅位更⼴也更灵活,适⽤于将读和写操作分开的场景(多线程读写规则见上⽂),
bool TryToAcquireObject(...)
{
// in this func, you can try to read specific object
shared_lock<shared_mutex> uLcM(itemMutex);
// here you can do something
}
void WriteObjectToCache(object obj, ...)
{
// in this func, you can write specific object to cache
unique_lock<shared_mutex> uLcM(itemMutex);
// here you can do something
}
2.2.2.C#
C#中的锁⽅式很多,可以直接对某个对象lock,也可以使⽤互斥量Mutex、Monitor等,
Mutex,参阅Microsoft官⽅⽂档⽰例如下,⽤法和c++的mutex没有本质的区别,
using System;
using System.Threading;
class Example
{
// Create a new Mutex. The creating thread does not own the mutex.
private static Mutex mut = new Mutex();
private const int numIterations = 1;
private const int numThreads = 3;
static void Main()
{
// Create the threads that will use the protected resource.
for(int i = 0; i < numThreads; i++)
{
Thread newThread = new Thread(new ThreadStart(ThreadProc));
newThread.Name = String.Format("Thread{0}", i + 1);
newThread.Start();
}
// The main thread exits, but the application continues to
// run until all foreground threads have exited.
// run until all foreground threads have exited.
}
private static void ThreadProc()
{
for(int i = 0; i < numIterations; i++)
{
UseResource();
}
}
// This method represents a resource that must be synchronized // so that only one thread at a time can enter.
private static void UseResource()
{
// Wait until it is safe to enter.
Console.WriteLine("{0} is requesting the mutex",
Thread.CurrentThread.Name);
mut.WaitOne();
Console.WriteLine("{0} has entered the protected area",
Thread.CurrentThread.Name);
// Place code to access non-reentrant resources here.
// Simulate some work.
Thread.Sleep(500);
Console.WriteLine("{0} is leaving the protected area",
Thread.CurrentThread.Name);
// Release the Mutex.
mut.ReleaseMutex();
Console.WriteLine("{0} has released the mutex",
Thread.CurrentThread.Name);
}
}
/
/ The example displays output like the following:
// Thread1 is requesting the mutex
// Thread2 is requesting the mutex
// Thread1 has entered the protected area
// Thread3 is requesting the mutex
// Thread1 is leaving the protected area
// Thread1 has released the mutex
// Thread3 has entered the protected area
// Thread3 is leaving the protected area
// Thread3 has released the mutex
// Thread2 has entered the protected area
/
/ Thread2 is leaving the protected area
// Thread2 has released the mutex
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论