首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【黄啊码】在C#中,如何使应用程序线程更加安全?

【黄啊码】在C#中,如何使应用程序线程更加安全?

原创
作者头像
黄啊码
发布2022-06-10 10:32:37
1.2K0
发布2022-06-10 10:32:37
举报

线程安全,特别是,它意味着它必须满足multithreading访问相同的共享数据的需要。 但是,这个定义似乎还不够。

任何人都可以列出的事情要做或照顾使应用程序线程安全 。 如果可能的话,就C / C ++语言给出一个答案。

函数可以有多种线程安全的方法。

它可以是可重入的 。 这意味着一个函数没有状态,不会触及任何全局variables或静态variables,所以它可以同时从多个线程中调用。 这个术语来自允许一个线程进入该function,而另一个线程已经在其中。

它可以有一个关键部分 。 这个术语很多,但坦率地说,我更喜欢关键的数据 。 当您的代码触及多个线程共享的数据时,就会出现关键部分。 所以我更愿意把重点放在那些关键数据上。

如果您正确使用互斥锁 ,则可以同步对关键数据的访问,从而妥善保护线程不安全的修改。 互斥和锁是非常有用的,但是强大的力量是很大的责任。 你不能在同一个线程中两次locking同一个互斥体(这是一个自我死锁)。 如果您获得多个互斥量,则必须小心,因为这会增加您陷入僵局的风险。 您必须始终如一地使用互斥锁来保护您的数据。

如果所有的函数都是线程安全的,并且所有的共享数据都得到了适当的保护,那么应用程序应该是线程安全的。

正如疯狂的艾迪所说,这是一个巨大的课题。 我build议阅读升压线程,并相应地使用它们。

低级警告 :编译器可以重新排列语句,这可以打破线程安全。 使用多个内核,每个内核都有自己的caching,并且需要正确同步caching才能保证线程安全。 另外,即使编译器不重新排列语句,硬件也可能会这样。 所以,充分,保证线程安全是不是今天实际上是可能的。 尽pipe如此,你可以获得99.99%的比例,而编译器厂商和CPU制造商正在努力解决这个徘徊的问题。

无论如何,如果你正在寻找一个清单,使一个类线程安全:

  • 识别跨线程共享的任何数据(如果您错过了,则无法保护)
  • 创build一个成员boost::mutex m_mutex ,并在你尝试访问共享成员数据时使用它(理想情况下,共享数据对于类是私有的,所以你可以更确定你是否正确保护它)。
  • 清理全局。 Globals反正是坏的,祝你好运,试图做全线程安全的事情。
  • 当心static关键字。 它实际上不是线程安全的。 所以如果你想要做一个单身人士,那么这样做是行不通的。
  • 谨防双重locking范式。 大多数使用它的人会以一些微妙的方式来错误的,而且由于低级警告而容易被破坏。

这是一个不完整的清单。 如果我想到的话,我会添加更多,但是希望这足以让你开始。

两件事情:

1.确保你不使用全局variables。 如果你现在有全局variables,使它们成为每线程状态结构的成员,然后让线程将结构传递给通用函数。

例如,如果我们从以下开始:

 // Globals int x; int y; // Function that needs to be accessed by multiple threads // currently relies on globals, and hence cannot work with // multiple threads int myFunc() { return x+y; } 

一旦我们添加一个状态结构的代码变成:

 typedef struct myState { int x; int y; } myState; // Function that needs to be accessed by multiple threads // now takes state struct int myFunc(struct myState *state) { return (state->x + state->y); } 

现在你可能会问,为什么不把x和y作为参数。 原因是这个例子是一个简化。 在现实生活中,你的状态结构可能有20个字段,并且通过这些参数的大部分4-5个函数变得令人望而生畏。 你宁愿传递一个参数而不是许多。

2.如果您的线程有共同的数据需要共享,那么您需要查看关键部分和信号量。 每次有一个线程访问数据时,都需要阻塞其他线程,然后在访问共享数据时解除阻塞。

如果你想独占访问类的方法,你必须在这些函数上使用锁。

不同types的锁:

使用atomic_flg_lck:

 class SLock { public: void lock() { while (lck.test_and_set(std::memory_order_acquire)); } void unlock() { lck.clear(std::memory_order_release); } SLock(){ //lck = ATOMIC_FLAG_INIT; lck.clear(); } private: std::atomic_flag lck;// = ATOMIC_FLAG_INIT; }; 

使用primefaces:

 class SLock { public: void lock() { while (lck.exchange(true)); } void unlock() { lck = true; } SLock(){ //lck = ATOMIC_FLAG_INIT; lck = false; } private: std::atomic<bool> lck; }; 

使用互斥体:

 class SLock { public: void lock() { lck.lock(); } void unlock() { lck.unlock(); } private: std::mutex lck; }; 

仅适用于Windows

 class SLock { public: void lock() { EnterCriticalSection(&g_crit_sec); } void unlock() { LeaveCriticalSection(&g_crit_sec); } SLock(){ InitializeCriticalSectionAndSpinCount(&g_crit_sec, 0x80000400); } private: CRITICAL_SECTION g_crit_sec; }; 

primefaces和atomic_flag保持线程在一个旋转计数。 互斥体只是睡觉的线程。 如果等待的时间太长,也许是更好的睡眠线程。 最后一个“ CRITICAL_SECTION ”保持线程在旋转计数直到消耗时间,然后线程进入睡眠。

如何使用这些关键部分?

 unique_ptr<SLock> raiilock(new SLock()); class Smartlock{ public: Smartlock(){ raiilock->lock(); } ~Smartlock(){ raiilock->unlock(); } }; 

使用raii成语。 构造函数locking关键部分和析构函数来解锁它。

 class MyClass { void syncronithedFunction(){ Smartlock lock; //..... } } 

这个实现是线程安全和exception安全的,因为variables锁被保存在堆栈中,所以当函数作用域结束时(函数结束或exception)析构函数将被调用。

我希望你觉得这有帮助。

谢谢!!

一个想法是把你的程序想象成一堆线程在队列中换行。 每个线程都有一个队列,这些队列将与所有线程共享(以及一个共享的数据同步方法(如互斥等))。

然后“解决”生产者/消费者问题,但是你想保持队列不被下溢或溢出。 en.wikipedia.org/wiki/Produc…

只要你保持你的线程本地化,只是通过在队列中发送拷贝来共享数据,而不是像multithreading中的(大多数)gui库和静态variables那样访问线程不安全的东西,那么你应该没问题。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.确保你不使用全局variables。 如果你现在有全局variables,使它们成为每线程状态结构的成员,然后让线程将结构传递给通用函数。
  • 2.如果您的线程有共同的数据需要共享,那么您需要查看关键部分和信号量。 每次有一个线程访问数据时,都需要阻塞其他线程,然后在访问共享数据时解除阻塞。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档