How to terminate a C++ std::thread?
C++ 11多线程入门 B站
C++11 并发指南
join():
函数阻塞线程,直到threadfunc()运行结束,回收对应创建线程的资源。如果不阻塞线程,就不能保证线程对象t1在threadfunc()运行期间有效,会引起程序崩溃。
detach():
调用detach()函数使得线程对象与线程函数分离,这样,线程对象与线程函数就没有联系了,此时的线程是作为后台线程去执行,detach()后就无法再和线程发生联系,也不能通过join()来等待线程执行完毕,线程何时执行完无法控制,它的资源会被init进程回收,所以,通常不采用detach()方法。
std::thread t1(std::bind(&A::threadfunc,&a));: 还可以通过std::bind来创建线程函数。创建一个类A,然后再main函数中将类A中的成员函数绑定到线程对象t1上。
互斥量(锁)
的使用,这是一种线程同步机制,在C++11中提供了4中互斥量。
std::mutex; //非递归的互斥量
std::timed_mutex; //带超时的非递归互斥量
std::recursive_mutex; //递归互斥量
std::recursive_timed_mutex; //带超时的递归互斥量
从各种互斥量的名字可以看出其具有的特性,在实际开发中,常用就是std::mutex,它就像是一把锁,我们需要做的就是对它进行加锁与解锁。
func()
{
//加锁
执行逻辑处理; //如果该过程抛出异常导致程序退出了,就没法unlock
//解锁
}
func()中再执行逻辑处理中程序因为某些原因退出了,此时就无法unlock()了,这样其他线程也就无法获取std::mutex,造成死锁现象,其实在加锁之前可以通过trylock()尝试一下能不能加锁。实际开发中,通常也不会这样写代码,而是采用lock_guard来控制std::mutex。
条件变量
是C++11提供的另外一种线程同步机制,通过判断条件是否满足,决定是否阻塞线程,当线程执行条件满足的时候就会唤醒阻塞的线程,常与std::mutex配合使用,C++11提供了两种条件变量。
1、std::condition_variable,配合std::unique_lockstd::mutex使用,通过wait()函数阻塞线程;
2、std::condition_variable_any,可以和任意带有lock()、unlock()语义的std::mutex搭配使用,比较灵活,但是其效率不及std::condition_variable;
std::unique_lock:C++11提供的 std::unique_lock是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。std::unique_lock比std::lock_guard使用更加灵活,功能更加强大。使用std::unique_lock需要付出更多的时间、性能成本。
下面利用std::mutex与std::condition_variable实现生产者与消费者模式。
std::list<std::shared_ptr<CTask>> g_task;
std::mutex g_mutex;
std::condition_variable g_conv;
//生产者线程
void ProdecerFunc()
{
int n_taskId = 0;
std::shared_ptr<CTask> ptask = nullptr;
while (true)
{
ptask = std::make_shared<CTask >(n_taskId); //创建任务
{
std::lock_guard<std::mutex> lock(g_mutex);
g_task.push_back(ptask);
std::cout << "produce a task Id is " << n_taskId << std::endl;
}
//唤醒线程
g_conv.notify_one();
n_taskId++;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
//消费者线程
void ConsumerFunc()
{
std::shared_ptr<CTask> ptask = nullptr;
while (true)
{
std::unique_lock<std::mutex> lock(g_mutex);
while (g_task.empty()) //即使被唤醒还要循环判断一次,防止虚假唤醒
{
g_conv.wait(lock);
}
ptask = g_task.front(); //取出任务
g_task.pop_front();
if (ptask == nullptr)
{
continue;
}
ptask->dotask(); //执行任务
}
}
int main()
{
std::thread t1(ConsumerFunc);
std::thread t2(ConsumerFunc);
std::thread t3(ConsumerFunc);
std::thread t4(ProdecerFunc);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
条件变量的使用过程可以归纳如下:
1、拥有条件变量的线消费者程获取互斥锁;
2、消费者线程循环检查条件是否满足,不满足则阻塞等待,此时释放互斥锁;
3、当生产者线程产生任务后,调用notify_one()或者notify_all()唤醒阻塞的消费者线程;
4、当消费者线程被唤醒后再次获得互斥锁去执行任务;
QT多线程
请注意,在构造函数中没有分配任何父函数,并且还确保将其定义为指针。这在我们打算使用moveToThread函数时是至关重要的。有父对象的对象不能移动到新线程中
第二个非常重要的注意点是不应该直接调用VideoProcessor的startVideo函数,而应通过将一个适当的信号连接到它进行调用。
即以这种方式启动线程之后,必须通过调用quit函数来终止该线程,并且在其对象中不应含有任何正在运行的循环或挂起的指令。如果不满足这两个条件中的任意一个,则在处理线程时,会遇到严重的问题。