在C++11中,作为程序猿的我们在编写多线程程序的时候不需要再像以前写个多线程程序那么复杂的调用一系列的系统层面的接口,比如类Unix系统下使用一堆的pthread的接口,在Windows下使用一堆的CreateTheread相关的接口。尤其在进入线程的互斥控制的时候,类Unix系统与Windows的使用更是差距较大。从而在开发的成本上来说,要做一个可移植的多线程程序,复杂度还是大大的上升了。但是在C++11中,我们再也不需要考虑这些问题了,在C++11中提供了统一的多线程库,从而在开发成本和可移植性上来说都有所提升。
C++11中提供的多线程的接口库提供的头文件有(参照:http://www.cplusplus.com/reference/multithreading/):
-
<atomic>
- Atomic (header) 原子操作
-
<thread>
- Thread (header) 线程
-
<mutex>
- Mutex (header) 互斥锁
-
<condition_variable>
- Condition variable (header) 条件变量
-
<future>
- Future (header)
以上五个头文件中,除了<thread>头文件外,其余的头文件都是提供线程同步处理的头文件。包括原子操作、互斥锁、条件变量等。Future不知道怎么用中文描述好一些,该功能主要用于的是C++11提供的异步处理,其提供的操作多为将来的将来的动作设置,比如std::promise里提供的接口中,可以设置指定变量在退出时的变量值等。更多的详细信息以及使用示例都可以参照C++11的手册上可以读到,并且网上应该也有很多的其他使用范例,不再赘述。
那么对于C++11提供的这么牛逼的线程功能,是否也同样存在诸多缺点呢?先分析以下代码:
#include <thread>
#include <mutex>
#include <deque>
#include <map>
#include <condition_variable>
#include <iostream>
class TestThread {
public:
static const size_t THREAD_COUNT = 10;
public:
void Init(int thread_count = THREAD_COUNT) {
for (auto i = 0; i < thread_count; ++i) {
std::thread t(&TestThread::ThreadWorking, this);
t.detach();
}
}
void AddVar(int var) {
std::cout << "_input_list size:" << _input_list.size() << " var:" << var << std::endl;
std::unique_lock<std::mutex> input_lck(_input_mtx);
_input_list.push_back(var);
_input_cv.notify_one();
}
void ThreadWorking() {
std::unique_lock<std::mutex> input_lck(_input_mtx);
while (true) {
_input_cv.wait(input_lck);
if (_input_list.empty()) {
std::this_thread::yield();
continue;
}
auto var = _input_list.front();
_input_list.pop_front();
std::unique_lock<std::mutex> output_lck(_output_mtx);
_output_list.push_back(var);
std::this_thread::yield();
}
}
void ProcessOnTick() {
if (!_input_list.empty()) {
std::unique_lock<std::mutex> input_lck(_input_mtx);
_input_cv.notify_one();
}
if (!_output_list.empty()) {
std::unique_lock<std::mutex> lck(_output_mtx);
for (auto var : _output_list) {
std::cout << "thread_id:" << std::this_thread::get_id() << " _output_list size:" << _output_list.size()
<< " var:" << var << std::endl;
}
_output_list.clear();
}
}
private:
std::mutex _input_mtx, _output_mtx;
std::condition_variable _input_cv;
std::deque<int> _input_list, _output_list;
};
int main() {
TestThread test_thread;
test_thread.Init(10);
auto var = 0;
while (true) {
while (true) {
auto random = std::rand() % 1000;
for (auto i = 0; i < random; ++i) {
test_thread.AddVar(var);
++var;
}
test_thread.ProcessOnTick();
}
}
}
这是一段极其简单的代码,从上述代码中着实看不出任何问题,无论是在锁的使用,还是在使用条件变量的时候。但实际在使用的过程中,使用的线程架构如上架构,但却出现了一种现象,就是主线程调用ProcessOnTick的频率大大降低。出现此类现象说明一个问题,就是在上述的循环中Add时的处理逻辑比较复杂,从而引发超时。因此在某些逻辑处理中,即使增加线程,可能也只能达到增加吞吐量的效果,并不能在效率上有所提升,并且可能还会引发性能下降的问题。因此在使用多线程处理时还需要更加慎重处理。(针对该代码实际还有一个更加严重的问题:_input_list会有大量的堆积数据来不及处理,如果发生长时间堆积,就会造成数据处理的严重超时,有兴趣的朋友可以分析一下为什么会出现堆积,并且可以怎么处理解决这个问题)
在这里,我首先想到了一个问题:C++11中默认使用的线程调度策略和优先级是什么?这个在诸多的文档中很难找到对应的文档进行说明,因此这个时候也暴露出了C++11中线程的问题:
使用简单了,可以从语言层面去解决线程的问题了。但是却缺少了从系统层面去使用线程的多样性,即在系统层面进行多线程编程时可以进行线程属性的多样性设置,包括现成的调度策略、线程的优先级等等。如类Unix环境下的pthread系列接口:
pthread_create():创建一个线程
pthread_exit():终止当前线程
pthread_cancel():中断另外一个线程的运行
pthread_join():阻塞当前的线程,直到另外一个线程运行结束
pthread_attr_init():初始化线程的属性
pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
pthread_attr_getdetachstate():获取脱离状态的属性
pthread_attr_destroy():删除线程的属性
pthread_kill():向线程发送一个信号
pthread_mutex_init() 初始化互斥锁
pthread_mutex_destroy() 删除互斥锁
pthread_mutex_lock():占有互斥锁(阻塞操作)
pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。即,当互斥锁空闲时,将占有该锁;否则,立即返回。
pthread_mutex_unlock(): 释放互斥锁
pthread_cond_init():初始化条件变量
pthread_cond_destroy():销毁条件变量
pthread_cond_signal(): 唤醒第一个调用pthread_cond_wait()而进入睡眠的线程
pthread_cond_wait(): 等待条件变量的特殊条件发生
Thread-local storage(或者以Pthreads术语,称作线程特有数据):
pthread_key_create(): 分配用于标识进程中线程特定数据的键
pthread_setspecific(): 为指定线程特定数据键设置线程特定绑定
pthread_getspecific(): 获取调用线程的键绑定,并将该绑定存储在 value 指向的位置中
pthread_key_delete(): 销毁现有线程特定数据键
pthread_attr_getschedparam();获取线程优先级
pthread_attr_setschedparam();设置线程优先级
pthread_equal(): 对两个线程的线程标识号进行比较
pthread_detach(): 分离线程
pthread_self(): 查询线程自身线程标识号
虽然这里的接口的条件变量、互斥锁以及线程的基础接口都在线程库里进行封装,但不知道为何,对于线程的优先级以及调度策略,我本人却没有找到对应的接口,看来还得继续研究研究,或者将pthread接口另作封装,或者直接使用。以此来达到相应的效果才是。