一、多线程概述
在多处理器或多核系统中,可以通过并行处理提升速度。开线程相比于开进程的开销更低,因为在进程中创建线程不会涉及初始化新的虚拟内存空间以及为进程创建环境,进程内所有的线程共享相同的虚拟地址空间。
Thread operations include thread creation, termination, synchronization (joins,blocking), scheduling, data management and process interaction.
A thread does not maintain a list of created threads, nor does it know the thread that created it.
All threads within a process share the same address space.
Threads in the same process share:
- Process instructions
- Most data
- open files (descriptors)
- signals and signal handlers
- current working directory
- User and group id
Each thread has a unique:
- Thread ID
- set of registers, stack pointer
- stack for local variables, return addresses
- signal mask
- priority
- Return value: errno
参考资料:https://www.cs.cmu.edu/afs/cs/academic/class/15492-f07/www/pthreads.html
二、基本操作
2.1 POSIX thread库支持C/C++的API , 为Unix系统支持的线程操作
pthread_create
//@param thread: returns the thread id.
//@param attr : Set to NULL if default thread attributes are used, else define members of the struct pthread_attr_t defined in bits/pthreadtypes.h
//@param void * (*start_routine) : pointer to the function to be threaded. Function has a single argument: pointer to void.
//@param *arg - pointer to argument of function. To pass multiple arguments, send a pointer to a structure.
//@return On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
pthread_join 子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源。
pthread_detach 主线程与子线程分离,子线程结束后,资源自动回收。线程执行完成后,会保留退出状态,等待别人来取。由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在,会产生僵尸进程。可以把线程置为detach状态,这样线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
2.2 std::thread
C++ 11中引入类std::thread,作为执行线程的抽象,其接口相对于pthread更好。
使用std::thread需要包含头文件 #include<thread>;std::thread内部实质也使用了pthread,因此,Cmakelist中的链接库需要包含pthread。
创建线程对象可采用如下方式:
int main()
{
//栈上
std::thread t1(print_thread); //根据函数初始化执行
std::thread t2(print_thread);
//线程数组
std::thread th[2]{std::thread(print_thread), std::thread(print_thread)};
//堆上
std::thread *pt1(new std::thread(print_thread));
std::thread *pt2(new std::thread(print_thread));
return 0;
}
std::join 是让当前主线程等待所有的子线程执行完,才能退出。
std::detach 子线程脱离主线程的绑定,主线程挂了,子线程不报错,子线程执行完自动退出。detach以后,子线程会成为孤儿线程,线程之间将无法通信。
三、线程同步
3.1 锁:防止多线程竞争
std::mutex:
- 如果mutex当前没有被任何线程lock,calling thread锁定它,该线程拥有mutex,直到调用unlock释放mutex;
- 如果mutext当前已经被其他线程lock,calling thread阻塞,直到其他线程unlock
std::mutex mutex_;
print_thread(){
mutex_.lock();
std::cout<<"this is a thread"<<std::endl;
mutex_unlock();
};
std::lock_guard对std::mutex做了封装,通过对象生命周期管理mutex,lock_guard被销毁时锁自动释放,防止mutex在核心区域内return等造成mutex无法释放。
std::mutex mutex_;
print_thread(){
{
std::lock_guard<std::mutex> lck(mutex_);
std::cout<<"this is a thread"<<std::endl;
}
};
std::unique_lock 可以像上面例子使用std::lock_guard一样使用std::unique_lock,另外,std::unique_lock相比于std::lock_guard有更多操作,例如,使用try_lock()方法尝试lock mutex,若lock失败(即mutex已经被其他线程lock),则直接跳过对临界区操作,而不是阻塞线程。
std::mutex mutex_;
print_thread(){
{
std::unique_lock<std::mutex> lck(mutex_, std::defer_lock);
if(lck.try_lock()){
std::cout<<"this is a thread"<<std::endl;
}
}
};
3.2 条件变量:多线程控制
condition_variable: 多线程通讯和控制,可以阻塞一个或同时阻塞多个线程。std::condition_variable需要与std::unique_lock配合使用。 调用condition_variable的wait方法时阻塞线程,并释放锁,使得其他运行的线程可以访问临界区;直到收到其他线程notify唤醒时,再次lock锁,并继续运行。
生产-消费模型:
class ProducerConsumerModel{
public:
void producer(){
while(true){
std::unique_lock<std::mutex> lck(mutex_);
while(wares_ready==true) produce.wait(lck);
i++;
wares=i;
wares_ready=true;
consume.notify_one();
}
}
bool consumer(){
while(true){
std::unique_lock<std::mutex> lck(mutex_);
while(wares_ready==false) consume.wait(lck);
std::cout<<wares<<std::endl;
wares_ready=false;
produce.notify_one();
return true;
}
}
private:
int wares; //需要被保护的对象,多进程中可能带来竞争;
std::mutex mutex_; //在多线程中用来做数据保护,保护buf;
bool wares_ready=false; //用来判断线程组合和唤醒的条件,使用全局变量或成员变量在线程间传递信息;
std::condition_variable consume, produce; //(根据wares_ready的条件)用来控制线程阻塞和唤醒;(condition_variable需要与std::unique_lock配合使用,unique_lock对mutex进行了浅层封装)
int i=0;
};
int main(int argc, char **argv){
ProducerConsumerModel prdcnsmodel;
std::thread threads[2];
threads[0]=std::thread(&ProducerConsumerModel::producer, &prdcnsmodel);
threads[1]=std::thread(&ProducerConsumerModel::consumer,&prdcnsmodel);
for(auto &t:threads){
t.join();
}
return 0;
}
以上是计划经济模型:生产一件、消费一件,consumer等producer生产完一件就消费一件,producer等consumer消费完一件再生产一件,保证没有浪费。
接下来我们再看另一种生产-消费模型,假设产品有保质期,为了满足消费需求,允许producer生产更多(producer线程运行更快),如果consumer没来得及消费,就直接丢弃,生产新的产品;但如果产能不足了(producer线程运行得更慢),consumer则必须等producer生产出来之后再消费。
class ProducerConsumerModel{
public:
void producer(){
while(true){
std::unique_lock<std::mutex> lck(mutex_,std::defer_lock);
if(lck.try_lock()){ //Tries to lock the associated mutex without blocking. Return true if the ownership of the mutex has been acquired successfully, false otherwise.
i++;
wares=i;
wares_ready=true;
consume.notify_one();
}
}
}
void consumer(){
while(true){
std::unique_lock<std::mutex> lck(mutex_);
consume.wait(lck,[this]{ return wares_ready==true});
std::cout<<wares<<std::endl;
wares_ready=false;
}
}
private:
int wares; //需要被保护的对象,多进程中可能带来竞争;
std::mutex mutex_; //在多线程中用来做数据保护,保护buf;
bool wares_ready=false; //用来判断线程组合和唤醒的条件,使用全局变量或成员变量在线程间传递信息;
std::condition_variable consume, produce; //(根据wares_ready的条件)用来控制线程阻塞和唤醒;(condition_variable需要与std::unique_lock配合使用,unique_lock对mutex进行了浅层封装)
int i=0;
};
int main(int argc, char **argv){
ProducerConsumerModel prdcnsmodel;
std::thread threads[2];
threads[0]=std::thread(&ProducerConsumerModel::producer, &prdcnsmodel);
threads[1]=std::thread(&ProducerConsumerModel::consumer,&prdcnsmodel);
for(auto &t:threads){
t.join();
}
return 0;
}
应用举例:在深度学习推理中,如果是使用离线图像进行测试,希望对每一张离线图像进行推理,那么需要生产一张、消费一张;在线使用摄像头数据进行推理时,由于摄像头更新快,深度学习推理慢,则仅对最新的图像进行推理。以上两个场景分别对应上面两种生产-消费模型。
参考资料:https://www.cnblogs.com/haippy/p/3252041.html