江门网站seo推广,互联网公司怎么起名字,网站证书打印格式不正确,wordpress2018主题一、多线程使用
多线程在实际的应用中非常广泛#xff0c;它在实际应用中遇到的主要的问题有以下几类#xff1a; 1、线程自身的控制 线程自身的控制包括#xff1a;线程结束控制#xff08;join/detach#xff09;#xff0c;所有权控制和数量选择。 2、线程的传参 线程…一、多线程使用
多线程在实际的应用中非常广泛它在实际应用中遇到的主要的问题有以下几类 1、线程自身的控制 线程自身的控制包括线程结束控制join/detach所有权控制和数量选择。 2、线程的传参 线程的传参一般是指线程初始启动时得到数据参数这个某些情况下很容易被忽略。 3、线程的同步 这是线程的复杂之处同步一般指多线程间要交互数据即使某些任务同步其实最终也要落实到数据的同步。 为了统一、方便分析使用本系列一般使用标准库的线程。
二、线程的管理
既然知道了线程的应用的场景就可以从这其中进行分析 1、线程的ID获得 主线程可以使用 auto id std::this_thread::get_id(); 子线程可以使用auto id th.get_id();//th是创建线程时的句柄 2、当前CPU支持的核心数量获取 获得当前最佳线程数量auto count std::thread::hardware_concurrency(); 这个需要说明的是在实际应用中要区分是CPU密集性应用还是IO密集性应用等情况这个数量才有参考意义。 3、线程的分离 这个非常简单
int data 0;
std::thread th std::thread([](int d) {std::cout this data is: d std::endl;},std::ref(data));
auto id th.get_id();
th.detach();
需要说明的是线程的分离的使用意味着线程需要自己控制状态否则会出现各种意想不到的结果。
4、线程的加入等待 看使用即可明白
int data 0;
std::thread th std::thread([](int d) {std::cout this data is: d std::endl;},std::ref(data));if (th.joinable()) {th.join();
}使用join的优势在于只要子线程不退出主线程不会退出。这意味什么一是线程被阻塞二是一旦有编码问题整个程序无法正常结束。 5、线程的控制 有的时候需要创建多个线程而这多个线程可能需要进行集中管理那么可以使用智能指针进行控制
#include iostream
#include thread
#include memory
#include vectorusing THREADPTR std::unique_ptrstd::thread;class ThreadManager {
public:ThreadManager() {};~ThreadManager() { this-Join(); };ThreadManager(const ThreadManager) delete;ThreadManager(const ThreadManager ) delete;ThreadManager operator(const ThreadManager) delete;ThreadManager operator(const ThreadManager) delete;int PutThread(THREADPTR tptr) { this-vecThread_.emplace_back(std::move(tptr)); return this-vecThread_.size(); }
private:void Join(){for (auto th : this-vecThread_){if (th-joinable()){th-join();}}};
private:std::vectorTHREADPTR vecThread_;
};
void ThreadManagerTest() {ThreadManager tm;std::unique_ptrstd::thread p1 std::make_uniquestd::thread([]() {std::cout run thread 1! std::endl; });std::unique_ptrstd::thread p2 std::make_uniquestd::thread([]() {std::cout run thread 2! std::endl;; });tm.PutThread(std::move(p1));tm.PutThread(std::move(p2));
}
int main()
{ThreadManagerTest();return 0;
}
三、线程的传参
在线程中传参有两种情况 1、直接传递 A、通过参数传递(值传递)
int data 0;
std::thread th std::thread([](int d) {std::cout this data is: d std::endl;},data);B、std::ref()传递引用传递
int data 0;
std::thread th std::thread([](int d) {std::cout this data is: d std::endl;},std::ref(data));引用传递一定要使用std::ref否则编译无法通过。
2、隐式传递 即使用Lambada表达式的[]的各种应用方式前面分析过这里不再讲解看一下例程就明白了
int data 0;
std::thread th std::thread([]() {std::cout this data is: data std::endl;});对一般的开发人员来说引用传参是比较容易被忽略的要引起注意。
四、线程的同步
线程的同步这里不做过多的分析下面会开专门的章节进行分析。其实同步的主要目的还是数据的原因如果单纯的同步其实应用场景还是比较少的。那么在多线程之间传递数据或者说共享数据可以使用几种方式来实现 1、各种同步机制 如std::mutex,std::condition_variable等在c更高版本11141720中还提供了shared_timed_mutex和shared_mutex等。同时还提供了对这些锁的管理类如std::lock_guard等看一个小例子
#include thread
#include mutex
#include iostreamint g_i 0;
std::mutex g_i_mutex; // protects g_ivoid safe_increment()
{std::lock_guardstd::mutex lock(g_i_mutex);g_i;std::cout std::this_thread::get_id() : g_i \n;// g_i_mutex is automatically released when lock// goes out of scope
}int main()
{std::cout __func__ : g_i \n;std::thread t1(safe_increment);std::thread t2(safe_increment);t1.join();t2.join();std::cout __func__ : g_i \n;
}2、原子锁 这个还是比较容易理解的
#include atomic
#include iostream
#include thread
#include vectorstd::atomic_int acnt;
int cnt;void f()
{for (int n 0; n 10000; n){acnt;cnt;// Note: for this example, relaxed memory order// is sufficient, e.g. acnt.fetch_add(1, std::memory_order_relaxed);}
}int main()
{{std::vectorstd::jthread pool;for (int n 0; n 10; n)pool.emplace_back(f);}std::cout The atomic counter is acnt \n The non-atomic counter is cnt \n;
}此处的代码使用了C20中的std::jthread,如果非要简单的来说明这个类可以理解为封装了std::thread,但在析构时自动调用join函数。不过它还是增加了其它的不少的函数比如在某些条件下可以对线程进行控制它的使用相对于std::thread要安全不少。
3、CAS无锁 在c编程中无锁编程一般应用在需要并发的数据结构处理中比如常见的链表、栈等看下面例子
#include atomictemplatetypename T
struct node
{T data;node* next;node(const T data) : data(data), next(nullptr) {}
};templatetypename T
class stack
{std::atomicnodeT* head;
public:void push(const T data){nodeT* new_node new nodeT(data);// 将 head 的当前值放到 new_node-next 中new_node-next head.load(std::memory_order_relaxed);// 现在令 new_node 为新的 head 但如果 head 不再是// 存储于 new_node-next 的值某些其他线程必须在刚才插入结点// 那么将新的 head 放到 new_node-next 中并再尝试while(!head.compare_exchange_weak(new_node-next, new_node,std::memory_order_release,std::memory_order_relaxed)); // 循环体为空// 注意上述使用至少在这些版本不是线程安全的
// 先于 4.8.3 的 GCC漏洞 60272先于 2014-05-05 的 clang漏洞 18899
// 先于 2014-03-17 的 MSVC漏洞 819819。下面是变通方法
// nodeT* old_head head.load(std::memory_order_relaxed);
// do
// {
// new_node-next old_head;
// }
// while (!head.compare_exchange_weak(old_head, new_node,
// std::memory_order_release,
// std::memory_order_relaxed));}
};int main()
{stackint s;s.push(1);s.push(2);s.push(3);
}无锁编程并非无锁只是把锁下移到了硬件控制中它的优势在于线程不睡眠有速度优势缺点是也是不睡眠浪费CPU。另外CAS无锁编程还有一个ABA现象一定要引起注意。所以其应用场景也比较明显需要数据吞吐量大最典型的就是股票行情应用上。如果数据单一读或写数据量大或者都不多大使用CAS并没有优势可能还会有劣势。所以还是那句话没有最好只有最合适。
4、特殊情况下的数据保护 如在一些单实例之类的单次应用中可以使用std::call_once配合 std::one_flag一起来使用。同时也可以使用局部静态变量等方式来实现数据在多线程的一次性实现。这类应用在网上和书上资料非常多此处就不举例了。
五、总结
本系列重点是如何写多线程的应用具体到一些细节的应用大家需要自己回头多看看相关资料或者书籍资料。如果有一些特别需要说明的可能会在后面插入一些说明的篇章但也不会安排在这个应用系列中。也就是说这里更侧重是对基础知识的应用在应用前要把应用的基础点都说明一下。