为什么我用C++的c++中sleep函数用法_for,回退时间不会阻塞?

this_thread::sleep_forstd::this_thread::sleep_for() 是 C++ 中的一个函数,它使当前线程暂停执行指定的时间。该函数接受一个时间段作为参数,指定线程需要休眠的时间长度。在暂停线程执行时,会让出当前线程的时间片,但并不会释放线程占用的资源。线程在休眠期间仍然会占用系统分配给它的资源,若在资源有限的环境中使用大量的休眠操作,可能会造成资源的浪费和效率问题#include <chrono> // 操作时间
#include <thread>
int main(int argc, char** argv)
{
std::this_thread::sleep_for(std::chrono::milliseconds(3)); // 毫秒
std::this_thread::sleep_for(std::chrono::seconds(3));
// 秒
std::this_thread::sleep_for(std::chrono::minutes(3));
// 分钟
std::this_thread::sleep_for(std::chrono::hours(3));
// 小时
}
boost::this_thread::sleep该函数将阻塞当前线程的执行,在睡眠期间不会占用系统资源,但会挂起当前线程的执行。因此,在使用该函数时应谨慎考虑应用的需要以及其他的线程和资源使用情况。 #include <boost/thread/thread.hpp>
boost::this_thread::sleep(boost::posix_time::seconds(3));
boost::this_thread::sleep(boost::posix_time::minutes(3));
boost::this_thread::sleep(boost::posix_time::hours(3));
}
线程库头文件头文件#include <thread>
命名空间std
创建线程示例#include <thread>
void fun()
{
//do some work
}
int main()
{
std::thread t(fun);
t.join();
}
在 上例 中,函数 fun将会运行于线程对象 t 中, join 函数将会阻塞main线程, 直到 t 线程执行结束注意:std:: thread 出了 作用域之后将会析 构, 这时如果线程函数还没有执行完则会发生错误, 因此,加个t.join, 保证主函数执行完毕前,t线程执行完毕thread类的成员函数joinvoid join();
//原型
等待线程完成其执行,即阻塞当前线程直至*this所标识的线程执行结束detachvoid detach();
容许线程从线程句柄独立开来执行从 thread 对象分离执行线程,允许执行独立地持续。一旦该线程退出,则由操作系统释放任何分配的资源。调用 detach 后 *this 不再占有任何线程。get_idstd::thread::id get_id() const noexcept
//noexcept表示当前函数不会抛出
返回标识与 *this 关联的线程的std::thread::id,注意这个线程id只是thread类内的那个成员id,非操作系统线程id示例:#include <iostream>
#include <thread>
#include <chrono>
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::thread t1(foo);
std::thread::id t1_id = t1.get_id();
std::thread t2(foo);
std::thread::id t2_id = t2.get_id();
std::cout << "t1's id: " << t1_id << '\n';
std::cout << "t2's id: " << t2_id << '\n';
t1.join();
t2.join();
}
joinablebool joinable() const noexcept;
检查 std::thread 对象是否标识活跃的执行线程,是返回true,否返回false,只要线程没有被撤销就属于活跃状态,内核对象存在就是活跃示例:void foo() { std::this_thread::sleep_for(std::chrono::seconds(1)); }
int main()
{
std::thread t;
std::cout << std::boolalpha << t.joinable() << std::endl;//false
t = std::thread(foo);
std::cout << std::boolalpha << t.joinable() << std::endl;//true
t.join();
std::cout << std::boolalpha << t.joinable() << std::endl;//false
}
//boolalpha的作用是使bool型变量按照false、true的格式输出。如不使用该标识符,那么结果会按照1、0的格式输出。
swapvoid swap( std::thread& other ) noexcept;
交换两个thread对象的底层句柄#include <iostream>
#include <thread>
#include <chrono>
void foo(){ std::this_thread::sleep_for(std::chrono::seconds(1));}
void bar(){ std::this_thread::sleep_for(std::chrono::seconds(1));}
int main()
{
std::thread t1(foo);
std::thread t2(bar);
std::cout << "thread 1 id: " << t1.get_id() << '\n'
<< "thread 2 id: " << t2.get_id() << '\n';
t1.swap(t2);
//等价于:std::swap(t1, t2);
std::cout << "after std::swap(t1, t2):" << '\n'
<< "thread 1 id: " << t1.get_id() << '\n'
<< "thread 2 id: " << t2.get_id() << '\n';
t1.join();
t2.join();
}
类外的swapvoid swap( std::thread &t1, std::thread &t2 ) noexcept;
std::swap(t1, t2);就等价于t1.swap(t2);this_thread命名空间的函数sleep_fortemplate< class Rep, class Period >
void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration );//参数是睡眠的时长
阻塞当前线程使其睡眠sleep_duration时间使用示例:std::this_thread::sleep_for(std::chrono::milliseconds(2000));//阻塞当前线程使其睡眠2000ms
sleep_untiltemplate< class Clock, class Duration >
void sleep_until( const std::chrono::time_point<Clock,Duration>& sleep_time );//参数是要阻塞到的时间点
阻塞当前线程,直至抵达指定的 sleep_time时间点yieldvoid yield() noexcept;
让当前线程让渡出自己的CPU时间片(给其他线程使用),避免一个线程频繁与其他线程争抢CPU时间片, 从而导致多线程处理性能下降使用示例:#include <iostream>
#include <chrono>
#include <thread>
void little_sleep(std::chrono::microseconds us)
{
auto start = std::chrono::high_resolution_clock::now();
//获取当前时间点
auto end = start + us;
do
{
std::this_thread::yield();
} while (std::chrono::high_resolution_clock::now() < end);
//end之前一直让渡时间片
}
int main()
{
auto
start = std::chrono::high_resolution_clock::now();
little_sleep(std::chrono::microseconds(100));
auto elapsed = std::chrono::high_resolution_clock::now() - start;//间隔的时间
std::cout << "waited for "
<< std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count()
<< " microseconds\n";
}
get_idstd::thread::id get_id() noexcept;
与上面thread类成员函数的get_id是同一个函数,只是因为它还是this_thread命名空间的,所以这里单独列出返回当前线程的std::thread::id,同样注意这个id不是操作系统的那个线程id使用示例:#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
std::mutex g_display_mutex;
void foo()
{
std::thread::id this_id = std::this_thread::get_id();
g_display_mutex.lock();
std::cout << "thread " << this_id << " sleeping...\n";
g_display_mutex.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::thread t1(foo);
std::thread t2(foo);
t1.join();
t2.join();
}
线程参数线程可以接受任意个数的参数,如下代码:#include <thread>
#include<string>
#include <iostream>
void func(int i, double d, const std::string& s)
{
std::cout << i << ", " << d << ", " << s << std::endl;
}
int main()
{
std::thread t(func, 1, 2, "test");
t.join();
return 0;
}
移动线程线程不能复制,但能移动,例如:#include <thread>
void func() {}
int main()
{
std::thread t1(func);
std::thread t2(std::move(t1));
t2.join();
return 0;
}
互斥量mutex头文件#include <mutex>
mutex 类是能用于保护共享数据免受多个线程同时访问的同步原语。mutex使用示例#include <thread>
#include <iostream>
#include <mutex>
int g_count = 0;
std::mutex g_mtx;
void fun()
{
g_mtx.lock();
for (int i = 0; i < 1000000; ++i)
{
++g_count;
}
g_mtx.unlock();
}
int main()
{
std::thread t1(fun);
std::thread t2(fun);
t1.join();
t2.join();
std::cout << g_count << std::endl;
}
mutex类的成员函数lockvoid lock();
锁定互斥。若另一线程已锁定互斥,则到 lock 的调用将阻塞执行,直至获得锁。注意:通常不直接调用 lock() :用 std::unique_lock或 std::lock_guard 管理排他性锁定。try_lockbool try_lock();
尝试锁定互斥。立即返回。成功获得锁时返回 true ,否则返回 falseunlockvoid unlock();
解锁互斥。互斥必须被当前执行线程所锁定,否则行为未定义。注意:通常不直接调用 lock() :用 std::unique_lock或 std::lock_guard 管理排他性锁定。std::lock_guard创建即加锁,作用域结束自动析构并解锁,无需手工解锁不能中途解锁,必须等待作用域结束才解锁不能复制利用RAII风格自动解锁类 lock_guard 是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。创建lock_guard对象std::mutex g_mtx;
std::lock_guard<std::mutex> lock(g_mtx);
使用示例:#include <thread>
#include <iostream>
#include <mutex>
int g_count = 0;
std::mutex g_mtx;
void fun()
{
std::lock_guard<std::mutex> lock(g_mtx);
for (int i = 0; i < 1000000; ++i)
{
++g_count;
}
}
int main()
{
std::thread t1(fun);
std::thread t2(fun);
t1.join();
t2.join();
std::cout << g_count << std::endl;
}
std::unique_lockunique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定可以随时加锁解锁作用域规则同 lock_grard,析构时自动释放锁不可复制,可移动当条件变量需要该类型的锁作为参数,必须使用unique_lock创建uniqiue_lock对象std::mutex mtx;
std::unique_lock<std::mutex> lock1(g_mtx);
使用示例:#include <thread>
#include <iostream>
#include <mutex>
int g_count = 0;
std::mutex g_mtx;
void fun()
{
std::unique_lock<std::mutex> lock(g_mtx);
for (int i = 0; i < 1000000; ++i)
{
++g_count;
}
}
int main()
{
std::thread t1(fun);
std::thread t2(fun);
t1.join();
t2.join();
std::cout << g_count << std::endl;
}
lock_guard与unique_lock主要区别unique_lock和lock_guard都是管理锁的辅助类工具,都是RAII风格;它们是在定义时获得锁,在析构时释放锁。它们的主要区别在于unique_lock锁机制更加灵活,可以再需要的时候进行lock或者unlock调用,不非得是析构或者构造时。它们的区别可以通过成员函数就可以一目了然条件变量
条件变量内容转自C++11条件变量使用详解
condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作,条件变量主要包括两个动作:当条件不满足时,相关线程被一直阻塞直到某种条件出现,这些线程才会被唤醒为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;通常情况下这个锁是std::mutex,并且管理这个锁 只能是 std::unique_lock<std::mutex> RAII模板类。等待条件成立使用的是condition_variable类成员wait 、wait_for 或 wait_until给出信号使用的是condition_variable类成员notify_one或者notify_all函数头文件#include <condition_variable>
使用示例condition_variable的成员函数notify_onevoid notify_one() noexcept;
若任何线程在 *this 上等待,则调用 notify_one 会唤醒一个阻塞的线程notify_allvoid notify_all() noexcept;
唤醒当前等待于 *this 的全部线程waitvoid wait( std::unique_lock<std::mutex>& lock );
template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate pred );//Predicate 谓词函数,可以普通函数或者lambda表达式
wait 导致当前线程阻塞直至条件变量被通知,或虚假唤醒发生,可循环wait直至满足某条件wait做的三件事:阻塞让锁再次持有锁时返回wait函数都在会阻塞时,自动释放锁权限,即调用unique_lock的成员函数unlock(),以便其他线程能有机会获得锁。这就是条件变量只能和unique_lock一起使用的原因,unique_lock锁机制相比于lock_guard更加灵活,可以在需要的时候进行lock或者unlock调用虚假唤醒在正常情况下,wait类型函数返回时要么是因为被唤醒,要么是因为超时才返回,但是在实际中发现,因为操作系统的原因,wait类型在不满足条件时,它也会返回,这就导致了虚假唤醒。因此,我们一般都是使用带有谓词参数的wait函数,因为这种(xxx, Predicate pred )类型的函数等价于:while (!pred()) //while循环,解决了虚假唤醒的问题
{
wait(lock);
}
如果虚假唤醒发生,由于while循环,再次检查条件是否满足,不满足就再次执行wait,解决了虚假唤醒生产者、消费者问题生产者、消费者问题:共享固定大小缓冲区的两个进程/线程,生产者往里面添加数据,消费者消耗数据该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。示例代码:std::mutex g_cvMutex;
std::condition_variable g_cv;
//缓存区
std::deque<int> g_data_deque;
//缓存区最大数目
const int
MAX_NUM = 30;
//数据
int g_next_index = 0;
//生产者,消费者线程个数
const int PRODUCER_THREAD_NUM
= 3;
const int CONSUMER_THREAD_NUM = 3;
void
producer_thread(int thread_id)
{
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
//加锁
std::unique_lock <std::mutex> lk(g_cvMutex);
//当队列未满时,继续添加数据
g_cv.wait(lk, [](){ return g_data_deque.size() <= MAX_NUM; });
g_next_index++;
g_data_deque.push_back(g_next_index);
std::cout << "producer_thread: " << thread_id << " producer data: " << g_next_index;
std::cout << " queue size: " << g_data_deque.size() << std::endl;
//唤醒其他线程
g_cv.notify_all();
//自动释放锁
}
}
void
consumer_thread(int thread_id)
{
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(550));
//加锁
std::unique_lock <std::mutex> lk(g_cvMutex);
//检测条件是否达成
g_cv.wait( lk,
[]{ return !g_data_deque.empty(); });
//互斥操作,消息数据
int data = g_data_deque.front();
g_data_deque.pop_front();
std::cout << "\tconsumer_thread: " << thread_id << " consumer data: ";
std::cout << data << " deque size: " << g_data_deque.size() << std::endl;
//唤醒其他线程
g_cv.notify_all();
//自动释放锁
}
}
int main()
{
std::thread arrRroducerThread[PRODUCER_THREAD_NUM];
std::thread arrConsumerThread[CONSUMER_THREAD_NUM];
for (int i = 0; i < PRODUCER_THREAD_NUM; i++)
{
arrRroducerThread[i] = std::thread(producer_thread, i);
}
for (int i = 0; i < CONSUMER_THREAD_NUM; i++)
{
arrConsumerThread[i] = std::thread(consumer_thread, i);
}
for (int i = 0; i < PRODUCER_THREAD_NUM; i++)
{
arrRroducerThread[i].join();
}
for (int i = 0; i < CONSUMER_THREAD_NUM; i++)
{
arrConsumerThread[i].join();
}
return 0;
}
参考文献
《深入应用C++11:代码优化与工程级应用》
cppreference.com
c++11中的lock_guard和unique_lock使用浅析
C++11条件变量使用详解
}
在C++学习过程中,要想“更上一层楼”的话,多线程编程是必不可少的一步。所以,我们在看这篇文章的时候,大家需要更多的思考是为什么这么做?这样做的好处是什么?以及多线程编程都可以应用在哪里?话不多说,跟着我一起认真探讨这块内容。1、多线程传统的C++(C++11标准之前)中并没有引入线程这个概念,在C++11出来之前,如果我们想要在C++中实现多线程,需要借助操作系统平台提供的API,比如Linux的<pthread.h>,或者windows下的<windows.h> 。C++11提供了语言层面上的多线程,包含在头文件<thread>中。它解决了跨平台的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。C++11 新标准中引入了5个头文件来支持多线程编程,如下图所示:1.1、多进程与多线程多进程并发使用多进程并发是将一个应用程序划分为多个独立的进程(每个进程只有一个线程),这些独立的进程间可以互相通信,共同完成任务。由于操作系统对进程提供了大量的保护机制,以避免一个进程修改了另一个进程的数据,使用多进程比使用多线程更容易写出相对安全的代码。但是这也造就了多进程并发的两个缺点:在进程间的通信,无论是使用信号、套接字,还是文件、管道等方式,其使用要么比较复杂,要么就是速度较慢或者两者兼而有之。运行多个线程的开销很大,操作系统要分配很多的资源来对这些进程进行管理。当多个进程并发完成同一个任务时,不可避免的是:操作同一个数据和进程间的相互通信,上述的两个缺点也就决定了多进程的并发并不是一个好的选择。所以就引入了多线程的并发。多线程并发多线程并发指的是在同一个进程中执行多个线程。优点:有操作系统相关知识的应该知道,线程是轻量级的进程,每个线程可以独立的运行不同的指令序列,但是线程不独立的拥有资源,依赖于创建它的进程而存在。也就是说,同一进程中的多个线程共享相同的地址空间,可以访问进程中的大部分数据,指针和引用可以在线程间进行传递。这样,同一进程内的多个线程能够很方便的进行数据共享以及通信,也就比进程更适用于并发操作。缺点:由于缺少操作系统提供的保护机制,在多线程共享数据及通信时,就需要程序员做更多的工作以保证对共享数据段的操作是以预想的操作顺序进行的,并且要极力的避免死锁(deadlock)。相关视频推荐多进程、多线程、线程使用场景分析高并发场景下,三种锁方案:互斥锁,自旋锁,原子操作的优缺点手把手实现线程池(120行),实现异步操作,解决项目性能问题 需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享1.2、多线程理解单CPU内核的多个线程。一个时间片运行一个线程的代码,并不是真正意义的并行计算。多个cpu或者多个内核可以做到真正的并行计算。1.3、创建线程创建线程很简单,只需要把函数添加到线程当中即可。形式1:std::thread myThread ( thread_fun);
//函数形式为void thread_fun()
myThread.join();
//同一个函数可以代码复用,创建多个线程形式2:std::thread myThread ( thread_fun(100));
myThread.join();
//函数形式为void thread_fun(int x)
//同一个函数可以代码复用,创建多个线程形式3:std::thread (thread_fun,1).detach();
//直接创建线程,没有名字
//函数形式为void thread_fun(int x)std::thread (thread_fun,1).detach();For Example使用g++编译下列代码的方式:g++ http://test.cc -o test -l pthread#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
cout<<"子线程1"<<endl;
}
void thread_2(int x)
{
cout<<"x:"<<x<<endl;
cout<<"子线程2"<<endl;
}
int main()
{
thread first ( thread_1); // 开启线程,调用:thread_1()
thread second (thread_2,100); // 开启线程,调用:thread_2(100)
//thread third(thread_2,3);//开启第3个线程,共享thread_2函数。
std::cout << "主线程\n";
first.join(); //必须说明添加线程的方式
second.join();
std::cout << "子线程结束.\n";//必须join完成
return 0;
}1.4、join与detach方式当线程启动后,一定要在和线程相关联的thread销毁前,确定以何种方式等待线程执行结束。比如上例中的join。detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。join方式,等待启动的线程完成,才会继续往下执行。可以使用joinable判断是join模式还是detach模式。if (myThread.joinable()) foo.join();(1)join举例下面的代码,join后面的代码不会被执行,除非子线程结束。#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
while(1)
{
//cout<<"子线程1111"<<endl;
}
}
void thread_2(int x)
{
while(1)
{
//cout<<"子线程2222"<<endl;
}
}
int main()
{
thread first ( thread_1); // 开启线程,调用:thread_1()
thread second (thread_2,100); // 开启线程,调用:thread_2(100)
first.join(); // pauses until first finishes 这个操作完了之后才能destroyed
second.join(); // pauses until second finishes//join完了之后,才能往下执行。
while(1)
{
std::cout << "主线程\n";
}
return 0;
}(2)detach举例下列代码中,主线程不会等待子线程结束。如果主线程运行结束,程序则结束。#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
while(1)
{
cout<<"子线程1111"<<endl;
}
}
void thread_2(int x)
{
while(1)
{
cout<<"子线程2222"<<endl;
}
}
int main()
{
thread first ( thread_1);
// 开启线程,调用:thread_1()
thread second (thread_2,100); // 开启线程,调用:thread_2(100)
first.detach();
second.detach();
for(int i = 0; i < 10; i++)
{
std::cout << "主线程\n";
}
return 0;
}1.5、this_threadthis_thread是一个类,它有4个功能函数,具体如下:函数使用说明get_idstd::this_thread::get_id()获取线程idyieldstd::this_thread::yield()放弃线程执行,回到就绪状态sleep_forstd::this_thread::sleep_for(std::chrono::seconds(1));暂停1秒sleep_until如下一分钟后执行吗,如下using std::chrono::system_clock;
std::time_t tt = system_clock::to_time_t(system_clock::now());
struct std::tm * ptm = std::localtime(&tt);
cout << "Waiting for the next minute to begin...\n";
++ptm->tm_min; //加一分钟
ptm->tm_sec = 0; //秒数设置为0//暂停执行,到下一整分执行
this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));2、mutexmutex头文件主要声明了与互斥量(mutex)相关的类。mutex提供了4种互斥类型,如下表所示。类型说明std::mutex最基本的 Mutex 类。std::recursive_mutex递归 Mutex 类。std::time_mutex定时 Mutex 类。std::recursive_timed_mutex定时递归 Mutex 类。std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。2.1、lock与unlockmutex常用操作:lock():资源上锁unlock():解锁资源trylock():查看是否上锁,它有下列3种类情况:(1)未上锁返回false,并锁住;(2)其他线程已经上锁,返回true;(3)同一个线程已经对它上锁,将会产生死锁。死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。下面结合实例对lock和unlock进行说明。同一个mutex变量上锁之后,一个时间段内,只允许一个线程访问它。例如:#include <iostream>
// std::cout
#include <thread>
// std::thread
#include <mutex>
// std::mutex
std::mutex mtx;
// mutex for critical section
void print_block (int n, char c)
{
// critical section (exclusive access to std::cout signaled by locking mtx):
mtx.lock();
for (int i=0; i<n; ++i)
{
std::cout << c;
}
std::cout << '\n';
mtx.unlock();
}
int main ()
{
std::thread th1 (print_block,50,'');//线程1:打印*
std::thread th2 (print_block,50,'$');//线程2:打印$
th1.join();
th2.join();
return 0;
}输出:**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$如果是不同mutex变量,因为不涉及到同一资源的竞争,所以以下代码运行可能会出现交替打印的情况,或者另一个线程可以修改共同的全局变量!#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex
std::mutex mtx_1; // mutex for critical section
std::mutex mtx_2;
// mutex for critical section
int test_num = 1;
void print_block_1 (int n, char c)
{
// critical section (exclusive access to std::cout signaled by locking mtx):
mtx_1.lock();
for (int i=0; i<n; ++i)
{
//std::cout << c;
test_num = 1;
std::cout<<test_num<<std::endl;
}
std::cout << '\n';
mtx_1.unlock();
}
void print_block_2 (int n, char c)
{// critical section (exclusive access to std::cout signaled by locking mtx):
mtx_2.lock();
test_num = 2;
for (int i=0; i<n; ++i)
{
//std::cout << c;
test_num = 2;
std::cout<<test_num<<std::endl;
}
mtx_2.unlock();
}
int main ()
{
std::thread th1 (print_block_1,10000,'*');
std::thread th2 (print_block_2,10000,'$');
th1.join();
th2.join();
return 0;
}2.2、lock_guard创建lock_guard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量。lock_guard的特点:创建即加锁,作用域结束自动析构并解锁,无需手工解锁不能中途解锁,必须等作用域结束才解锁不能复制代码举例#include <thread>
#include <mutex>
#include <iostream>
int g_i = 0;
std::mutex g_i_mutex;
// protects g_i,用来保护g_i
void safe_increment()
{
const std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
std::cout << std::this_thread::get_id() << ": " << g_i << '\n';// g_i_mutex自动解锁}int main(){
std::cout << "main id: " <<std::this_thread::get_id()<<std::endl;
std::cout << "main: " << g_i << '\n';
std::thread t1(safe_increment);
std::thread t2(safe_increment);
t1.join();
t2.join();
std::cout << "main: " << g_i << '\n';
}说明:该程序的功能为,每经过一个线程,g_i 加1。因为涉及到共同资源g_i ,所以需要一个共同mutex:g_i_mutex。main线程的id为1,所以下次的线程id依次加1。2.3、unique_lock简单地讲,unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更加灵活方便,能够应对更复杂的锁定需要。unique_lock的特点:创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定可以随时加锁解锁作用域规则同 lock_grard,析构时自动释放锁不可复制,可移动条件变量需要该类型的锁作为参数(此时必须使用unique_lock)所有 lock_guard 能够做到的事情,都可以使用 unique_lock 做到,反之则不然。那么何时使lock_guard呢?很简单,需要使用锁的时候,首先考虑使用 lock_guard,因为lock_guard是最简单的锁。下面是代码举例:#include <mutex>
#include <thread>
#include <iostream>
struct Box {
explicit Box(int num) : num_things{num} {}
int num_things;
std::mutex m;
};
void transfer(Box &from, Box &to, int num)
{
// defer_lock表示暂时unlock,默认自动加锁
std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);//两个同时加锁
std::lock(lock1, lock2);//或者使用lock1.lock()
from.num_things -= num;
to.num_things += num;//作用域结束自动解锁,也可以使用lock1.unlock()手动解锁
}
int main()
{
Box acc1(100);
Box acc2(50);
std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);
t1.join();
t2.join();
std::cout << "acc1 num_things: " << acc1.num_things << std::endl;
std::cout << "acc2 num_things: " << acc2.num_things << std::endl;
}说明:该函数的作用是,从一个结构体中的变量减去一个num,加载到另一个结构体的变量中去。std::mutex m;在结构体中,mutex不是共享的。但是只需要一把锁也能锁住,因为引用传递后,同一把锁传给了两个函数。cout需要在join后面进行,要不然cout的结果不一定是最终算出来的结果。std::ref 用于包装按引用传递的值。std::cref 用于包装按const引用传递的值。3、condition_variablecondition_variable头文件有两个variable类,一个是condition_variable,另一个是condition_variable_any。condition_variable必须结合unique_lock使用。condition_variable_any可以使用任何的锁。下面以condition_variable为例进行介绍。condition_variable条件变量可以阻塞(wait、wait_for、wait_until)调用的线程直到使用(notify_one或notify_all)通知恢复为止。condition_variable是一个类,这个类既有构造函数也有析构函数,使用时需要构造对应的condition_variable对象,调用对象相应的函数来实现上面的功能。类型说明condition_variable构建对象析构删除waitWait until notifiedwait_forWait for timeout or until notifiedwait_untilWait until notified or time pointnotify_one解锁一个线程,如果有多个,则未知哪个线程执行notify_all解锁所有线程cv_status这是一个类,表示variable 的状态,如下所示enum class cv_status { no_timeout, timeout };3.1、wait当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程。在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait()函数也是自动调用 lck.lock(),使得lck的状态和 wait 函数被调用时相同。代码示例:#include <iostream>
// std::cout
#include <thread>
// std::thread, std::this_thread::yield
#include <mutex>
// std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
std::mutex mtx;
std::condition_variable cv;
int cargo = 0;
bool shipment_available()
{
return cargo!=0;
}
void consume (int n)
{
for (int i=0; i<n; ++i)
{
std::unique_lock<std::mutex> lck(mtx);//自动上锁
//第二个参数为false才阻塞(wait),阻塞完即unlock,给其它线程资源
cv.wait(lck,shipment_available);// consume:
std::cout << cargo << '\n';
cargo=0;
}
}
int main ()
{
std::thread consumer_thread (consume,10);
for (int i=0; i<10; ++i)
{
//每次cargo每次为0才运行。
while (shipment_available())
std::this_thread::yield();
std::unique_lock<std::mutex> lck(mtx);
cargo = i+1;
cv.notify_one();
}
consumer_thread.join();
return 0;
}
说明:主线程中的while,每次在cargo=0才运行。每次cargo被置为0,会通知子线程unblock(非阻塞),也就是子线程可以继续往下执行。子线程中cargo被置为0后,wait又一次启动等待。也就是说shipment_available为false,则等待。3.2、wait_for与std::condition_variable::wait() 类似,不过 wait_for可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for返回,剩下的处理步骤和 wait()类似。template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep,Period>& rel_time);另外,wait_for 的重载版本的最后一个参数pred表示 wait_for的预测条件,只有当 pred条件为false时调用 wait()才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred为 true时才会被解除阻塞。template <class Rep, class Period, class Predicate>
bool wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep,Period>& rel_time, Predicate pred);代码示例:#include <iostream>
// std::cout
#include <thread>
// std::thread
#include <chrono>
// std::chrono::seconds
#include <mutex>
// std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable, std::cv_status
std::condition_variable cv;
int value;
void read_value()
{
std::cin >> value;
cv.notify_one();
}
int main ()
{
std::cout << "Please, enter an integer (I'll be printing dots): \n";
std::thread th (read_value);
std::mutex mtx;
std::unique_lock<std::mutex> lck(mtx);
while (cv.wait_for(lck,std::chrono::seconds(1))==std::cv_status::timeout)
{
std::cout << '.' << std::endl;
}
std::cout << "You entered: " << value << '\n';
th.join();
return 0;
}通知或者超时都会解锁,所以主线程会一直打印。示例中只要过去一秒,就会不断的打印。4、线程池4.1、概念在一个程序中,如果我们需要多次使用线程,这就意味着,需要多次的创建并销毁线程。而创建并销毁线程的过程势必会消耗内存,线程过多会带来调动的开销,进而影响缓存局部性和整体性能。线程的创建并销毁有以下一些缺点:创建太多线程,将会浪费一定的资源,有些线程未被充分使用。销毁太多线程,将导致之后浪费时间再次创建它们。创建线程太慢,将会导致长时间的等待,性能变差。销毁线程太慢,导致其它线程资源饥饿。线程池维护着多个线程,这避免了在处理短时间任务时,创建与销毁线程的代价。4.2、线程池的实现因为程序边运行边创建线程是比较耗时的,所以我们通过池化的思想:在程序开始运行前创建多个线程,这样,程序在运行时,只需要从线程池中拿来用就可以了.大大提高了程序运行效率.一般线程池都会有以下几个部分构成:线程池管理器(ThreadPoolManager):用于创建并管理线程池,也就是线程池类工作线程(WorkThread): 线程池中线程任务队列task: 用于存放没有处理的任务。提供一种缓冲机制。append:用于添加任务的接口线程池实现代码:#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <iostream>
#include <stdexcept>
#include <condition_variable>
#include <memory> //unique_ptr
#include<assert.h>
const int MAX_THREADS = 1000; //最大线程数目
template <typename T>
class threadPool
{
public:
threadPool(int number = 1);//默认开一个线程
~threadPool();
std::queue<T > tasks_queue; //任务队列
bool append(T *request);//往请求队列<task_queue>中添加任务<T >
private:
//工作线程需要运行的函数,不断的从任务队列中取出并执行
static void *worker(void arg);
void run();
private:
std::vector<std::thread> work_threads; //工作线程
std::mutex queue_mutex;
std::condition_variable condition;
//必须与unique_lock配合使用
bool stop;
};//end class//构造函数,创建线程
template <typename T>
threadPool<T>::threadPool(int number) : stop(false)
{
if (number <= 0
number > MAX_THREADS)
throw std::exception();
for (int i = 0; i < number; i++)
{
std::cout << "created Thread num is : " << i <<std::endl;
work_threads.emplace_back(worker, this);
//添加线程
//直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
}
}
template <typename T>
inline threadPool<T>::~threadPool()
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
condition.notify_all();
for (auto &ww : work_threads)
ww.join();//可以在析构函数中join
}
//添加任务
template <typename T>
bool threadPool<T>::append(T *request)
{
//操作工作队列时一定要加锁,因为他被所有线程共享
queue_mutex.lock();//同一个类的锁
tasks_queue.push(request);
queue_mutex.unlock();
condition.notify_one();
//线程池添加进去了任务,自然要通知等待的线程
return true;
}//单个线程
template <typename T>
void threadPool<T>::worker(void *arg)
{
threadPool pool = (threadPool *)arg;
pool->run();//线程运行
return pool;
}
template <typename T>
void threadPool<T>::run()
{
while (!stop)
{
std::unique_lock<std::mutex> lk(this->queue_mutex);
/* unique_lock() 出作用域会自动解锁 /
this->condition.wait(lk, [this]
{
return !this->tasks_queue.empty();
});//如果任务为空,则wait,就停下来等待唤醒//需要有任务,才启动该线程,不然就休眠
if (this->tasks_queue.empty())//任务为空,双重保障
{
assert(0&&"断了");//实际上不会运行到这一步,因为任务为空,wait就休眠了。
continue;
}else{
T *request = tasks_queue.front();
tasks_queue.pop();
if (request)//来任务了,开始执行
request->process();
}
}
}
#endif说明:构造函数创建所需要的线程数一个线程对应一个任务,任务随时可能完成,线程则可能休眠,所以任务用队列queue实现(线程数量有限),线程用采用wait机制。任务在不断的添加,有可能大于线程数,处于队首的任务先执行。只有添加任务(append)后,才开启线程condition.notify_one()。wait表示,任务为空时,则线程休眠,等待新任务的加入。添加任务时需要添加锁,因为共享资源。测试代码:#include "mythread.h"
#include<string>
#include<math.h>
using namespace std;
class Task
{
public:
void process()
{
//cout << "run........." << endl;//测试任务数量
long i=1000000;
while(i!=0)
{
int j = sqrt(i);
i--;
}
}
};
int main(void){
threadPool<Task> pool(6);//6个线程,vector
std::string str;
while (1)
{
Task *tt = new Task();//使用智能指针
pool.append(tt);//不停的添加任务,任务是队列queue,因为只有固定的线程数
cout<<"添加的任务数量:"<<pool.tasks_queue.size()<<endl;
delete tt;
}
}这篇文章到此就结束了,希望大家能够有非常大的收获。}

我要回帖

更多关于 c++中sleep函数用法 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信