使用std::future和std::packaged_task实现的异步threadpool 使用C++封装pthread
参考:C++11之获取线程返回值 C++11多线程-异步运行(1)之std::promise [C++11 并发指南四( 详解一 std::promise 介绍)](https://www.cnblogs.com/haippy/p/3239248.html) C++11 Multithreading – Part 8: std::future , std::promise and Returning values from Thread C++11 并发编程系列(四):异步操作(future) csdn:C++11新特性之 std::future and std::async C++11 Concurrency Tutorial - Part 5: Futures
在C++11中关于并发编程的特性有以下相关内容: 静态变量在多线程中的初始化是线程安全的 thread_local保证在每个线程中都会创建一个该对象,保证相关内容是可重入的 对于标准容器和迭代器: 对于标准输入输出(std::cin,std::cout,std::cerr)是可以进行并发访问的,但是它可能导致字符交错。但是在流上进行没有同步的并发访问将是未定义的行为 对于默认分配器(见第19章)的所有成员函数(析构函数除外),并发访问是同步的。
boost共享锁实现 读写锁可以分为:公平锁,读优先,写优先,优先级锁等。Linux系统提供了pthread_rwlock系列函数作为读写锁的实现,同样的Boost库提供了share_lock作为读写锁实现的辅助类。C++标准库没有提供读写锁,但是我们可以实用mutex和condition_variable来很容易的实现读写锁。 boost::shared_lock std::unique_lock std::lock_guard pthread_rwlock_init pthread_rwlock_destroy pthread_rwlock_rdlock pthread_rwlock_wrlock pthread_rwlock_unlock 读写锁实现思路 公平锁:实用队列来管理所,先到先得 读优先:这种场合用于写少读多的情况,只要有读请求则写请求永远等待 写优先:这种场合和读优先相反,只要有写请求,则读永远被阻塞 优先级锁:带有优先级的锁,优先级高的锁先获取资源,可以使用set管理请求资源的锁,并按照优先级排序 公平锁 其实,标准库提供的mutex就是一种公平锁,因为被唤醒的线程是随机的。如果强调真正意义的公平,则可以使用队列来管理锁,只有处于队列头的锁才能获取资源。其实Linux系统已经实现了公平锁——在pthread_mutex初始化时传入参数mutexattr,其包含如下几种: PTHREAD_MUTEX_TIMED_NP ,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。 PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。 PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。 PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。 因此,只需要在创建mutex的时候指定 PTHREAD_MUTEX_TIMED_NP 属性即可。 读优先/写优先 读优先和写优先的本质都是一样的。在Linux中有线程的读写锁,同样的也可以指定读写锁的属性。 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); attr 共有 3 种选择 PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况 PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 PTHREAD_RWLOCK_PREFER_READER_NP 一致 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁 这里用C++的mutex和condition_variable实现一个写优先的锁。满足以下逻辑: 当读锁占用锁的时候,其他线程也可以获取读锁 当写锁占用锁的时候,其他任何线程既不可以获取读锁,也不可以获取写锁 当有写锁在等待的时候,优先唤醒写锁 class write_priotity_lock { public: void read_lock() { std::unique_lock<std::mutex> lock(m_mutex); m_read_cv.wait(lock,[this](){ return this->m_write_count == 0; }); m_read_count++; } void write_lock() { std::unique_lock<std::mutex> lock(m_mutex); m_write_count++; m_write_cv.wait(lock,[this](){ return this->m_write_count <= 1 && this->m_read_count == 0; }); } void read_release() { --m_read_count; if(m_read_count == 0 && m_write_count > 0) { m_write_cv.notify_one(); } } void write_release() { --m_write_count; if(m_write_count >= 1) { m_write_cv.notify_one(); } else { m_read_cv.notify_all(); } } private: std::condition_variable m_write_cv; std::condition_variable m_read_cv; std::atomic<int32_t> m_read_count; std::atomic<int32_t> m_write_count; std::mutex m_mutex; };
自旋锁、读写锁、信号量的实现,欢迎参考我的gitee源码
在早期的clib中存在很多非线程安全的函数。 比如: #include <string.h> char *strerror(int errnum); 这个版本的strerror使用一个静态的char *来存放错误信息,如果在多线程的情况下则可能出现不可预知的情况。要解决这个问题有几种方法: 提供额外的函数,由调用者提供buf,而不是使用库的静态变量的buf 在每个线程中实例化各自的buf,比如使用 __thread、thread_local或者pthread_getspecific 返回局部的非静态变量 用户自己提供buf 在新版本的函数库中,比如 timelocal 为了线程安全就是需要调用者自己提供buf的,新旧接口如下: #include <time.h> // #include <ctime> struct tm* __cdecl localtime(__time64_t const* _Time); static __inline errno_t localtime_s(struct tm*const _Tm,time_t const* const _Time); localtime返回的是一个静态或全局唯一的struct tm指针,因此是非线程安全的。而localtime_s返回struct tm的buf是由调用者自己提供的,因此是线程安全的。 线程中实例化各自buf 参考-stackoverflow 如果为了方便函数调用,不希望调用者自己去提供buf,简化编程,可以由库的编写者将变量声明为线程安全的,可以使用下面3个修饰符: __thread thread_local pthread_getspecific 要使用他们需要引入pthread.h头文件,并且pthread_getspecific是最慢的,不建议使用。 Recent GCC, e.g. GCC 5 do support C11 and its thread_local (if compiling with e.g. gcc -std=c11). As FUZxxl commented, you could use (instead of C11 thread_local) the __thread qualifier supported by older GCC versions. Read about Thread Local Storage. pthread_getspecific is indeed quite slow (it is in the POSIX library, so is not provided by GCC but e.g. by GNU glibc or musl-libc) since it involves a function call. Using thread_local variables will very probably be faster. Look into the source code of MUSL’s thread/pthread_getspecific.c file for an example of implementation. Read this answer to a related question. And _thread & thread_local are (often) not magically translated to calls to pthread_getspecific. They usually involve some specific address mode and/or register (details are implementation specific, related to the ABI; on Linux, I guess that since x86-64 has more registers & address modes, its implementation of TLS is faster than on i386), with help from the compiler, the linker and the runtime system. It could happen on the contrary that some implementations of pthread_getspecific are using some internal thread_local variables (in your implementation of POSIX threads). As an example, compiling the following code #include <pthread.h> const extern pthread_key_t key; __thread int data; int get_data (void) { return data; } int get_by_key (void) { return *(int*) (pthread_getspecific (key)); } //using GCC 5.2 (on Debian/Sid) with gcc -m32 -S -O2 -fverbose-asm gives the following code for get_data using TLS: // .type get_data, @function // get_data: // .LFB3: // .cfi_startproc // movl %gs:data@ntpoff, %eax # data, // ret // .cfi_endproc // and the following code of get_by_key with an explicit call to pthread_getspecific: // get_by_key: // .LFB4: // .cfi_startproc // subl $24, %esp #, // .cfi_def_cfa_offset 28 // pushl key # key // .cfi_def_cfa_offset 32 // call pthread_getspecific # // movl (%eax), %eax # MEM[(int *)_4], MEM[(int *)_4] // addl $28, %esp #, // .cfi_def_cfa_offset 4 // ret // .cfi_endproc Hence using TLS with __thread (or thread_local in C11) should probably be faster than using pthread_getspecific (avoiding the overhead of a call). Notice that thread_local is a convenience macro defined in (a C11 standard header). 在C++11之前,如果一个线程初始化某一资源后要在其他线程中使用该资源需要由程序员自己保证资源的初始化在使用之前,另外一种情况是某一资源初始化代码可能会被多次执行也需要程序员自己去保证代码的同步。在C++11中引入了static初始化安全保证以及call_once函数保证资源只被初始化一次。 static变量的线程安全 C++11标准保证局部的static变量初始化在多线程环境中的安全。如下: call_once 和 once_flag C++标准库提供了std::once_flag和std::call_once条件竞争,它一般应用于条件初始化中。比起锁住互斥量,并显式的检查指针,每个线程只需要使用std::call_once,在std::call_once的结束时,它保证相关资源已经被安全的初始化完成了。使用std::call_once比显式使用互斥量消耗的资源更少,特别是当初始化完成后。 call_once调用后为啥资源就是已经线程安全的初始化了呢??实际上就是cpp保证了std::call_once的调用和执行是线程安全的,至于你要在该函数里面做什么,那是你自己的事。如果你没有在里面初始化相关的资源,那么程序肯定是不能按照你的预期执行的。 std::shared_ptr<some_resource> resource_ptr; std::once_flag resource_flag; // 1 void init_resource() { resource_ptr.reset(new some_resource); } void foo() { std::call_once(resource_flag,init_resource); // 可以完整的进行一次初始化 resource_ptr->do_something(); } thread_local关键字 thread_local关键字用于定义线程本地变量。当一个变量被定义为thread_local的时候,系统会在每个运行的线程中都实例化出一个该变量。它可以很方便的在多线程环境中获取正在运行的线程中相关的信息。
参考-知乎
造成死锁的原因很多,总的一句话就是资源不可用。在多线程中,造成死锁的原因无外乎多个线程相互之间等待自己没有的资源。避免死锁的建议:
条件变量在生产者-消费者模型中的使用
C++11 没有提供信号量,但是可以使用条件变量和互斥锁很容易的实现信号量。信号量是用来在多线程中进行资源同步的。信号量内部维护资源的数量,并且提供2个操作——wait和signal,wait的时候获取资源并减少计数器,signal的时候释放资源并增加计数器。只有当计数器的数目>0的情况下去wait才能够获取到资源。