友汇网站建设,上海企业网站建设,怎样做网站赚流量,厦门网站建设方案文章目录 C11 线程库线程对象的构造方式无参的构造函数调用带参的构造函数调用移动构造函数thread常用成员函数 this_thread命名空间join detachmutex C11 线程库
线程对象的构造方式
无参的构造函数
1、调用无参的构造函数,调用无参的构造函数创建出来的线程对象… 文章目录 C11 线程库线程对象的构造方式无参的构造函数调用带参的构造函数调用移动构造函数thread常用成员函数 this_thread命名空间join detachmutex C11 线程库
线程对象的构造方式
无参的构造函数
1、调用无参的构造函数,调用无参的构造函数创建出来的线程对象没有关联任何线程函数即没有启动任何线程
thread t1;2、thread提供了移动赋值函数当后续需要让该线程对象与线程函数关联时可以以带参的方式创建一个匿名对象然后调用移动赋值将该匿名对象关联线程的状态转移给该线程对象
void Print(size_t n , const std::string s)
{for (size_t i 0; i n; i){std::cout std::this_thread::get_id() : i std::endl;}
}
int main()
{int n 10;// 创建n个线程执行Printstd::vectorstd::thread vthd(n);size_t j 0;for (auto thd : vthd){// 移动赋值thd std::thread(Print, 10, 线程 std::to_string(j));}for (auto thd : vthd){thd.join();}return 0;}场景
实现线程池的时候, 需要先创建一批线程但,一开始这些线程什么也不做当有任务到来时再让这些线程来处理这些任务。
#include iostream
#include vector
#include thread
#include functional
#include queue
#include mutex
#include condition_variable
#include atomicclass ThreadPool {
public:ThreadPool(size_t threads 4); // 构造函数可以指定线程池的大小~ThreadPool(); // 析构解函数void enqueue(std::functionvoid() task); // 添加任务到线程池private:std::vectorstd::thread workers; // 线程向量std::queuestd::functionvoid() tasks; // 任务队列队std::mutex queuestd::functionvoid() completedTasks;std::condition_variable cond; // 条件变量bool stop; // 停止标志void work(); // 线程工作函数
};// 构造函数初始化线程池
ThreadPool::ThreadPool(size_t threads) : stop(false) {for(size_t i 0; i threads; i)workers.emplace_back(std::thread(ThreadPool::work, this));
}// 析构解函数等待所有线程完成
ThreadPool::~ThreadPool() {{std::unique_lockstd::mutex lock(this-mtx);this-stop true;}cond.notify_all();for(std::thread worker : workers) {if(worker.joinable()) {worker.join(); // 等待线程结束}}
}// 添加任务到线程池
void ThreadPool::enqueue(std::functionvoid() task) {{std::unique_lockstd::mutex lock(mtx);if(stop)throw std::runtime_error(enqueue on stopped ThreadPool);tasks.emplace(task);}cond.notify_one();
}void ThreadPool::work() {while(true) {std::functionvoid() task;{std::unique_lockstd::mutex lock(this-mtx);while(tasks.empty()) {cond.wait(lock); // 如果没有任务等待}task std::move(tasks.front());tasks.pop();}();task(); // 执行任务}
}在这个示例中ThreadPool类管理一组工作线程。构造函数创建指定数量的线程每个线程都调用work方法等待并执行任务。enqueue方法用于添加新任务到队列如果线程池已经停止则抛出异常。每个工作线程在接收到任务后会出队列并执行它
调用带参的构造函数
template class Fn, class... Args
explicit thread (Fn fn, Args... args);
fn可调用对象比如函数指针、仿函数、lambda表达式、被包装器包装后的可调用对象等。args...调用可调用对象fn时所需要的若干参数
调用移动构造函数
用一个右值线程对象来构造一个线程对象
void func(int n)
{for (int i 0; i n; i){cout i endl;}
}
int main()
{thread t3 thread(func, 10);t3.join();return 0;
}
线程是操作系统中的一个概念线程对象可以关联一个线程用来控制线程以及获取线程的状态。 如果创建线程对象时没有提供线程函数那么该线程对象实际没有对应任何线程。 如果创建线程对象时提供了线程函数那么就会启动一个线程来执行这个线程函数该线程与主线程一起运行。 thread类是防拷贝的不允许拷贝构造和拷贝赋值但是可以移动构造和移动赋值可以将一个线程对象关联线程的状态转移给其他线程对象并且转移期间不影响线程的执行
thread常用成员函数
join ,对该线程进行等待在等待的线程返回之前调用join函数的线程将会被阻塞 joinable , 判断该线程是否已经执行完毕如果是则返回true否则返回false detach ,将该线程与创建线程进行分离被分离后的线程不再需要创建线程调用join函数对其进行等待 get_id , 获取该线程的id swap , 将两个线程对象关联线程的状态进行交换
joinable函数还可以用于判定线程是否是有效的
如果是以下任意情况则线程无效
采用无参构造函数构造的线程对象。该线程对象没有关联任何线程 线程对象的状态已经转移给其他线程对象。已经将线程交给其他线程对象管理 线程已经调用join或detach结束。线程已经结束
thread的成员函数get_id可以获取线程的id但该方法必须通过线程对象来调用get_id函数
如果要在线程对象关联的线程函数中获取线程id调用this_thread命名空间下的get_id函数
void func()
{cout this_thread::get_id() endl; //获取线程id
}
int main()
{thread t(func);t.join();return 0;
}
this_thread命名空间
yield当前线程“放弃”执行让操作系统调度另一线程继续执行sleep_until让当前线程休眠到一个具体时间点sleep_for让当前线程休眠一个时间段
线程传参
1、使用lambda
#includethread
#includeiostream
#includevector
#includestringint main()
{size_t n1 5;size_t n2 5;/*std::cin n1 n2;*/std::thread t1( [n1](){for (size_t i 0; i n1; i){//拿到该线程的线程idstd::cout std::this_thread::get_id() : i std::endl;}} );std::thread t2([n2](){for (size_t i 0; i n2; i){//拿到该线程的线程idstd::cout std::this_thread::get_id() : i std::endl;}} );t2.join();t1.join();return 0;
}join detach
启动一个线程后当这个线程退出时需要对该线程所使用的资源进行回收否则可能会导致内存泄露等问题。thread库提供了两种回收线程资源的方式
1、join
主线程创建新线程后调用join函数等待新线程终止当新线程终止时join函数就会自动清理线程相关的资源
join函数清理线程的相关资源后thread对象与已销毁的线程就没有关系了因此一个线程对象一般只会使用一次join如果一个线程对象使用多次join , 程序会崩溃
void func(int n)
{for (int i 0; i n; i){cout i endl;}
}
int main()
{thread t(func, 20);t.join();t.join(); //程序崩溃return 0;
}
2、detach
主线程创建新线程后也可以调用detach函数将新线程与主线程进行分离分离后新线程会在后台运行其所有权和控制权将会交给C运行库此时C运行库会保证当线程退出时其相关资源能够被正确回收。
使用detach的方式回收线程的资源一般在线程对象创建好之后就立即调用detach函数。 否则线程对象可能会因为某些原因在后续调用detach函数分离线程之前被销毁掉这时就会导致程序崩溃。 因为当线程对象被销毁时会调用thread的析构函数而在thread的析构函数中会通过joinable判断这个线程是否需要被join如果需要那么就会调用terminate终止当前程序程序崩溃
#include iostream
#include thread
#include vectorvoid worker() {std::cout Working... std::endl;// 模拟耗时操作std::this_thread::sleep_for(std::chrono::seconds(2));std::cout Finished std::endl;
}int main() {std::vectorstd::thread threads;// 创建并分离多个线程for (int i 0; i 5; i) {threads.emplace_back(std::thread(worker));threads.back().detach(); // 分离线程}std::cout Main thread continues to run... std::endl;// 主线程可以继续执行其他任务而不需要等待分离的线程// 等待所有线程完成可选for (auto th : threads) {if (th.joinable()) {th.join();}}std::cout All threads finished std::endl;return 0;
}过度使用分离线程可能会导致资源泄露因为分离的线程将继续运行即使主线程已经结束。因此通常建议在可能的情况下使用join来管理线程的生命周期。
mutex
C11中mutex中总共包了四种互斥量
1、std::mute
mutex锁是C11提供的最基本的互斥量mutex对象之间不能进行拷贝也不能进行移动
mutex常用的成员函数
lock对互斥量进行加锁try_lock尝试对互斥量进行加锁unlock对互斥量进行解锁释放互斥量的所有权
线程函数调用lock时可能会发生以下三种情况
1、如果该互斥量当前没有被其他线程锁住则调用线程将该互斥量锁住直到调用unlock之前该线程一致拥有该锁。
#include iostream
#include thread
#include mutexstd::mutex mtx; // 全局互斥锁void printBlock(int n) {mtx.lock(); // 获取锁如果锁已经被其他线程占用则等待for (int i 0; i n; i) {std::cout Thread std::this_thread::get_id() says i std::endl;}mtx.unlock(); // 释放锁其他线程可以获取该锁
}int main() {std::thread t1(printBlock, 1);std::thread t2(printBlock, 2);t1.join();t2.join();return 0;
}mtx是一个全局互斥锁所以一次只能有一个线程执行printBlock函数。这意味着即使两个线程几乎同时运行输出也将是交错的而不是同时打印因为一个线程在打印时会锁定互斥锁另一个线程必须等待
2、如果该互斥量已经被其他线程锁住则当前的调用线程会被阻塞。
互斥锁mutex在多线程环境中的基本行为。互斥锁是一种同步机制用于防止多个线程同时访问共享资源从而避免数据竞争条件和不一致的状态。当一个线程尝试获取已经被其他线程持有的互斥锁时该线程将被阻塞直到互斥锁被释放
#include iostream
#include thread
#include mutex
#include chronostd::mutex mtx; // 全局互斥锁void worker(int id) {// 尝试获取锁mtx.lock();std::cout Thread id acquired the lock std::endl;// 模拟耗时的工作std::this_thread::sleep_for(std::chrono::milliseconds(500));std::cout Thread id released the lock std::endl;// 释放锁mtx.unlock();
}int main() {std::thread t1(worker, 1);std::thread t2(worker, 2);// 主线程稍作等待以便 t1 有机会获取锁std::this_thread::sleep_for(std::chrono::milliseconds(100));// 尝试在 t1 持有锁时获取锁mtx.lock();std::cout Main thread acquired the lock std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟主线程持有锁mtx.unlock();t1.join();t2.join();return 0;
}3、如果该互斥量被当前调用线程锁住则会产生死锁deadlock。
如何使用局部变量加锁
例1用lambda
int main()
{size_t n1 10000;size_t n2 10000;/*std::cin n1 n2;*/int x 0;std::mutex mtx;std::thread t1([n1, x ,mtx]() {for (size_t i 0; i n1; i){mtx.lock();x;//拿到该线程的线程id//std::cout std::this_thread::get_id() : i std::endl;mtx.unlock();}});std::thread t2([n2, x,mtx]() {for (size_t i 0; i n2; i){mtx.lock();//拿到该线程的线程idx;//std::cout std::this_thread::get_id() : i std::endl;mtx.unlock();}});t2.join();t1.join();std::cout x;return 0;
}例2
void Print1(size_t n,size_t j , const std::string s ,std::mutex mtx) //锁必须传引用 锁不支持拷贝
{for (size_t i 0; i jn; i){mtx.lock();std::cout std::this_thread::get_id() : i std::endl;mtx.unlock();}
}
int main()
{int n 10;// 创建n个线程执行Printstd::vectorstd::thread vthd(n);std::mutex mtx;std::thread t1(Print1, 100, 1, hello, ref(mtx )); //必须加ref函数 否则锁不能以传引用的方式传参传过去std::thread t2(Print1, 100, 100000, world, ref(mtx));t1.join();t2.join();return 0;}例3
void Print1(size_t n, const std::string s, std::mutex mtx ,int rx) //锁必须传引用 锁不支持拷贝
{for (size_t i 0; i n; i){mtx.lock();std::cout std::this_thread::get_id() : i std::endl;rx;mtx.unlock();std::this_thread::sleep_for(std::chrono::microseconds(1000));}
}int main()
{std::mutex mtx;int x 10;std::thread t1(Print1, 5, hello, std::ref(mtx), std::ref(x));std::thread t2(Print1,5, world, std::ref(mtx), std::ref(x));std::cout 线程1: t1.get_id() std::endl;std::cout 线程2: t2.get_id() std::endl;t1.join();t2.join();std::cout x std::endl;return 0;
}线程调用try_lock时类似也可能会发生以下三种情况
1、如果该互斥量当前没有被其他线程锁住则调用线程将该互斥量锁住直到调用unlock之前该线程一致拥有该锁。
2、如果该互斥量已经被其他线程锁住则try_lock调用返回false当前的调用线程不会被阻塞。
3、如果该互斥量被当前调用线程锁住则会产生死锁deadlock