当前位置: 首页 > news >正文

利用第三方做网站永久发布地址跨境购网站建设

利用第三方做网站永久发布地址,跨境购网站建设,网站首页新世纪建设集团有限公司,200万做网站多线程#xff08;C#xff09; 文章目录 多线程#xff08;C#xff09;前言一、std::thread类1.线程的创建1.1构造函数1.2代码演示 2.公共成员函数2.1 get_id()2.2 join()2.3 detach()2.4 joinable()2.5 operator 3.静态函数4.类的成员函数作为子线程的任务函数 二、call…多线程C 文章目录 多线程C前言一、std::thread类1.线程的创建1.1构造函数1.2代码演示 2.公共成员函数2.1 get_id()2.2 join()2.3 detach()2.4 joinable()2.5 operator 3.静态函数4.类的成员函数作为子线程的任务函数 二、call_once函数1.原理和介绍2.应用-懒汉模式 三、线程同步之互斥锁(互斥量)1.std::mutex类1.1 成员函数1.2 线程同步 2.std::lock_guard类3.std::recursive_mutex类4.std::timed_mutex类 四、线程同步之条件变量1.生产者-消费者模型2.条件变量2.1 成员函数 总结 前言 C11之前C语言没有对并发编程提供语言级别的支持这使得我们在编写可移植的并发程序时存在诸多的不便。现在C11中增加了线程以及线程相关的类很方便地支持了并发编程使得编写的多线程程序的可移植性得到了很大的提高。 一、std::thread类 1.线程的创建 1.1构造函数 //1. 默认构造函数构造一个线程对象在这个线程中不执行任何处理动作 thread() nonexcept;//2. 移动构造函数将other的线程所有权转移给新的thread对象之后的other不再表示执行线程 thread(thread other) nonexcept;//3. 创建线程对象并在该线程中执行函数f的业务逻辑args是要传递给函数f的参数使用最多的最有用的 //此处使用右值引用的好处可以延长临时变量的寿命等到使用完成后再进行释放 templateclass Function,class... Args explicit thread(Function f,Args... args); /*备注 任务函数f的类型可以是如下 1.普通函数、类成员函数、匿名函数、仿函数这些都是可调用对象类型 2.可以是可调用对象包装器类型,也可以是使用绑定器绑定之后得到的类型仿函数 *///4.使用delet显示删除拷贝构造不允许线程对象之间的拷贝 thread(const thread)delete;1.2代码演示 #include iostream #include threadvoid func() {std::cout children worker: xiaodu ,id: std::this_thread::get_id() std::endl; }void func1(std::string name,int id) {std::cout children worker: name ,id: std::this_thread::get_id() std::endl; }int main() {std::cout 主线程id: std::this_thread::get_id() std::endl;//1. 创建空的线程对象std::thread t1;//2. 创建一个可用的子线程std::thread t2(func);std::thread t3(func1, xiaoliu, 20);std::thread t4([](int id) {std::cout arg id : id ,id: std::this_thread::get_id() std::endl;}, 18);std::thread t5 std::move(t4);return 0; }报错原因分析程序启动之后执行main()函数进程里就有一个主线程父线程然后接下来打印主线程id再接下来创建线程t1、t2、t3、t4、t5但是子线程创建后就会变成就绪态和主线程一起抢cpu时间片谁抢到cpu时间片谁就执行对应的任务函数主线程大概率抢到cpu时间片主线程执行完就退出了随之退出的还有虚拟地址空间因此子线程就算抢到cpu时间片但是对应的虚拟地址空间已经没了所以报错。 报错的解决思路让主线程等待子线程执行完任务函数再退出。 2.公共成员函数 2.1 get_id() 应用程序启动后默认只有一个线程这个线程一般称为主线程或父线程通过线程类创建出来的线程一般称为子线程每个线程创建出来都对应一个线程ID这个ID是唯一的可以通过这个ID来区分和识别各个已经存在的线程实例这个函数就是get_id()函数原型如下 // std::thread::id--长整型数 std::thread::id get_id() const noexcept;示例程序 #include iostream #include threadvoid func(int num,std::string str) {for (int i 0; i 10; i){std::cout 子线程i i num : num ,str: str std::endl;} }void func1() {for (int i 0; i 10; i){std::cout 子线程i i std::endl;} }int main() {// 获得线程ID的两种方法// 方法1--在子线程或者主线程函数中调用std::this_thread::get_id()函数会得到当前线程IDstd::cout 主线程id: std::this_thread::get_id() std::endl;std::thread t(func,520,i love you);std::thread t1(func1);// 方法2--调用thread类的成员函数get_id()std::cout 线程t的线程ID t.get_id()std::endl;std::cout 线程t1的线程ID t1.get_id() std::endl;return 0; }2.2 join() join()字面意思是连接一个线程意味着主动地等待线程的终止线程阻塞。在某个线程A中通过子线程对象B调用join()函数调用这个函数的线程A被阻塞但是子线程对象B中的任务函数会继续执行当任务执行完毕之后join()会清理当前子线程B中的相关资源然后返回同时调用该函数的线程A解除阻塞继续向下执行。 // 如果要阻塞主线程的执行只需要在主线程中通过子线程对象调用这个方法即可 // 当调用这个方法的子线程对象中的任务函数执行完毕之后主线程的阻塞也就随之解除了 void std::thread::join();示例程序1 #include iostream #include threadvoid func() {std::cout children worker: xiaodu ,id: std::this_thread::get_id() std::endl; }void func1(std::string name,int id) {std::cout children worker: name ,id: std::this_thread::get_id() std::endl; }int main() {std::cout 主线程id: std::this_thread::get_id() std::endl;//1. 创建空的线程对象std::thread t1;//2. 创建一个可用的子线程std::thread t2(func);std::thread t3(func1, xiaoliu, 20);std::thread t4([](int id) {std::cout arg id : id ,id: std::this_thread::get_id() std::endl;}, 18);std::thread t5 std::move(t4);t2.join();t3.join();t5.join(); // t4线程的所有权转交给t5,t4对象失效return 0; }当程序运行到thread::join()时存在两种情况 如果任务函数func()还没执行完毕主线程阻塞知道任务执行完毕主线程解除阻塞继续向下运行如果任务函数func()已经执行完毕主线程不会阻塞继续向下执行 上述的bug通过join()函数成功解决。 示例程序2 需求程序中一共有三个线程其中两个子线程负责分段下载同一个文件下载完毕之后由主线程对这个文件进行下一步处理 #include iostream #include thread #include chrono using namespace std;void download1() {// 模拟下载, 总共耗时500ms阻塞线程500msthis_thread::sleep_for(chrono::milliseconds(500));cout 子线程1: this_thread::get_id() , 下载1....完成 endl; }void download2() {// 模拟下载, 总共耗时300ms阻塞线程300msthis_thread::sleep_for(chrono::milliseconds(300));cout 子线程2: this_thread::get_id() , 下载2....完成 endl; }void doSomething() {cout 完成.... endl; }int main() {thread t1(download1);thread t2(download2);// 阻塞主线程等待所有子线程任务执行完毕再继续向下执行t1.join();t2.join();doSomething(); }最核心的处理是在主线程调用doSomething();之前在第35、36行通过子线程对象调用了join()方法这样就能够保证两个子线程的任务都执行完毕了也就是文件内容已经全部下载完成主线程再对文件进行后续处理如果子线程的文件没有下载完毕主线程就去处理文件很显然从逻辑上讲是有问题的。 2.3 detach() detach()函数的作用是进行线程分离分离主线程和创建出的子线程。在线程分离之后主线程退出也会一并销毁创建出的所有子线程在主线程退出之前它可以脱离主线程继续独立的运行任务执行完毕之后这个子线程会自动释放自己占用的系统资源。(唯一好处)比如孩子子线程翅膀硬了和家里父线程断绝关系detach自己外出闯荡(脱离主线程执行任务函数)死了家里也不会给买棺材执行完毕后自己释放系统资源如果家里被诛九族主线程退出还是会受到牵连子线程还是会退出。 示例程序 #include iostream #include threadvoid func() {std::cout children worker: xiaodu ,id: std::this_thread::get_id() std::endl; }void func1(std::string name, int id) {std::cout children worker: name ,id: std::this_thread::get_id() std::endl; }int main() {std::cout 主线程id: std::this_thread::get_id() std::endl;//1. 创建空的线程对象std::thread t1;//2. 创建一个可用的子线程std::thread t2(func);std::thread t3(func1, xiaoliu, 20);std::thread t4([](int id){std::cout arg id : id ,id: std::this_thread::get_id() std::endl;}, 18);std::thread t5 std::move(t4);t2.join();t3.join();// 子线程t5在主线程中调用detach()函数子线程和主线程分离//从此再也不能在主线程中子线程调用成员函数进行任何有效操作,否则会报错t5.detach();// 不能得到真正的线程IDt5.get_id();// 失效报错//t5.join(); return 0; }注意事项线程分离函数detach()不会阻塞线程子线程和主线程分离之后在主线程中就不能再对这个子线程做任何控制了比如通过join()阻塞主线程等待子线程中的任务执行完毕或者调用get_id()获取子线程的线程ID。有利就有弊鱼和熊掌不可兼得建议使用join()一般不用detach()。 2.4 joinable() joinable()函数用于判断主线程和子线程是否处理关联连接状态一般情况下二者之间的关系处于关联状态该函数返回一个布尔类型 返回值为true主线程和子线程之间有关联连接关系返回值为false主线程和子线程之间没有关联连接关系 bool joinable() const noexcept;示例程序 #include iostream #include thread #include chrono using namespace std;void foo() {std::this_thread::sleep_for(chrono::seconds(1)); }int main() {// 创建一个空的线程对象如果子线程未关联任务函数则二者也是未关联thread t;cout before starting,joinable: t.joinable() endl; //false// 匿名对象作为临时变量使用右值引用拷贝构造函数t thread(foo);cout after starting,joinable: t.joinable() endl; //true// 线程t调用jion函数阻塞主线程执行任务函数完成后退出子线程t.join();cout after starting,joinable: t.joinable() endl; //false// 创建线程t1 thread t1(foo);cout after starting,joinable: t1.joinable() endl; //true// 线程分离t1.detach();cout after starting,joinable: t1.joinable() endl; //falsereturn 0; }2.5 operator 线程中的资源是不能被复制的因此通过操作符进行赋值操作最终并不会得到两个完全的对象。 // move(1) thread operator(thread other) noexcept; // copy[deleted](2) thread operator(thread other) noexcept;通过以上操作符的重载声明可以得知 如果other是一个右值会将资源所有权进行转移如果other不是一个右值禁止拷贝该函数显示被删除delete不可用 3.静态函数 thread线程类还提供了一个静态方法用于获取当前计算机的CPU核心数根据这个结果在程序中创建出数量相等的线程每个线程独自占有一个CPU核心这些线程就不用分时复用CPU时间片此时程序的并发效率是最高的(并行)。 static unsigned hardware_concurrency() noexcept;示例代码 #include iostream #include thread using namespace std;int main() {unsigned int num thread::hardware_concurrency();cout hardware_concurrency num endl;return 0; }4.类的成员函数作为子线程的任务函数 待学TODO 二、call_once函数 1.原理和介绍 在某些特定情况下某些函数只能在多线程环境下调用一次比如要初始化某个对象而这个对象只能被初始化一次就可以使用std::call_once()来保证函数在多线程环境下只能被调用一次。使用call_once()的时候需要一个once_flag作为call_once()的传入参数该函数的原型如下 // 定义位于mutex #include mutex void call_once(std::once_flag flag,Callable f,Args... args) flagonce_flag类型的对象要保证这个对象能够被多个线程同时访问到f回调函数可以传递一个有名函数地址也可以指定一个匿名函数args作为实参传递给回调函数 多线程操作过程中std::call_once()内部的回调函数只会被执行一次示例代码如下 #include iostream #include thread #include mutex using namespace std;// 全局变量 once_flag flag;void do_once() {cout do once function endl; }void do_something() {static int num 1;// call_once注意事项flag要能被多个线程同时访问传入实参参数的形式类似thread函数无返回值void// 使用场景在多线程中只需要初始化一次对象new 一个对象call_once(flag, do_once);cout do something function: num endl; }int main() {thread t1(do_something);thread t2(do_something);thread t3(do_something);t1.join();t2.join();t3.join();return 0; }2.应用-懒汉模式 单例模式分类 懒汉式 系统运行中实例并不存在只有当需要使用该实例时才会去创建并使用实例。这种方式要考虑线程安全。饿汉式 系统一运行就初始化创建实例当需要时直接调用即可。这种方式本身就线程安全没有多线程的线程安全问题。 编写一个单例模式分为懒汉式和饿汉式的类–懒汉模式。 示例程序 #include iostream #include thread #include mutex using namespace std;// 单例模式--懒汉模式 // 全局变量保证被多个线程访问 once_flag g_flag;// 单例类 class Base { public:// 实现单例模式首先将拷贝构造和赋值操作显示删除Base(const Base obj) delete;Base operator(const Base obj) delete;// 静态成员函数static Base* getInstance() {// 调用call_once函数保证多个线程访问时只有一个对象创建call_once(g_flag, []() {ptr new Base;cout 单例创建 endl;});return ptr;}void release(){delete ptr;ptr nullptr;}string getName(){return name;}void setName(string Name){this-name Name;}private:// 构造函数设为private不能被外界直接访问Base() { }~Base() { }static Base* ptr;string name; }; // 懒汉模式是运行时才会创建对象所以ptr先设置为nullptr Base* Base::ptr nullptr;void func(string name) {Base::getInstance()-setName(name);std::coutname: Base::getInstance()-getName()endl; }int main() {thread t1(func,路飞);thread t2(func,埃斯);thread t3(func,萨博);t1.join();t2.join();t3.join();Base::getInstance()-release();return 0; }此处很大概率会出现打印信息不对因为三个线程同时对一块内存进行了读写操作所以为了让多线程线性访问需要加锁。 加锁后的代码程序 #include iostream #include thread #include mutex using namespace std;// 单例模式--懒汉模式 // 全局变量保证被多个线程访问 once_flag g_flag;// 单例类 class Base { public:// 实现单例模式首先将拷贝构造和赋值操作显示删除Base(const Base obj) delete;Base operator(const Base obj) delete;// 静态成员函数static Base* getInstance() {// 调用call_once函数保证多个线程访问时只有一个对象创建call_once(g_flag, []() {ptr new Base;cout 单例创建 endl;});return ptr;}void release(){delete ptr;ptr nullptr;}string getName(){return name;}void setName(string Name){this-name Name;}private:// 构造函数设为private不能被外界直接访问Base() { }~Base() { }static Base* ptr;string name; }; // 懒汉模式是运行时才会创建对象所以ptr先设置为nullptr Base* Base::ptr nullptr;mutex mx; void func(string name) {// 多个线程的共享资源是Base::getInstance()mx.lock();Base::getInstance()-setName(name);std::coutname: Base::getInstance()-getName()endl;mx.unlock(); }int main() {thread t1(func,路飞);thread t2(func,埃斯);thread t3(func,萨博);t1.join();t2.join();t3.join();Base::getInstance()-release();return 0; }三、线程同步之互斥锁(互斥量) 进行多线程编程如果多个线程需要对同一块内存进行操作比如同时读、同时写、同时读写对于后两种情况来说如果不做任何的人为干涉就会出现各种各样的错误数据。这是因为线程在运行的时候需要先得到CPU时间片时间片用完之后需要放弃已获得的CPU资源就这样线程频繁地在就绪态和运行态之间切换更复杂一点还可以在就绪态、运行态、挂起态之间切换这样就会导致线程的执行顺序并不是有序的而是随机的混乱的。 注意概念线程同步是指让多线程进行线性有序的进行而不是同时对一块内存操作。 解决多线程数据混乱的方案就是进行线程同步最常用的就是互斥锁在C11中一共提供了四种互斥锁 std::mutex独占的互斥锁不能递归使用 最常用std::timed_mutex带超时的独占互斥锁不能递归使用std::recursive_mutex递归互斥锁不带超时功能std::recursive_timed_mutex带超时的递归互斥锁 注意事项内存可能涉及到多个线程的同时访问写、读写,此时需要通过互斥锁对内存进行保护使用互斥锁锁住的是和共享数据相关的一个代码块临界区在程序中一个共享数据对应多个代码块在锁住这些代码块的时候要用同一把锁并且程序运行期间不能销毁这把互斥锁 1.std::mutex类 1.1 成员函数 lock()函数用于给临界区加锁并且只能有一个线程获得锁的所有权它有阻塞其他线程的作用函数原型如下 // void lock();独占互斥锁对象有两种状态锁定和未锁定。如果互斥锁是打开的调用lock()函数的线程会得到互斥锁的所有权并将其上锁其它线程再调用该函数的时候由于得不到互斥锁的所有权就会被lock()函数阻塞。当拥有互斥锁所有权的线程将互斥锁解锁此时被lock()阻塞的线程解除阻塞抢到互斥锁所有权的线程加锁并继续运行没抢到互斥锁所有权的线程继续阻塞。 除了使用lock()还可以使用try_lock()获取互斥锁的所有权并对互斥锁加锁函数原型如下 bool try_lock();二者的区别在于try_lock()不会阻塞线程lock()会阻塞线程 如果互斥锁是未锁定状态得到了互斥锁所有权并加锁成功函数返回true如果互斥锁是锁定状态无法得到互斥锁所有权加锁失败函数返回false **当互斥锁被锁定之后可以通过unlock()进行解锁但是需要注意的是只有拥有互斥锁所有权的线程也就是对互斥锁上锁的线程才能将其解锁其它线程是没有权限做这件事情的。**该函数的函数原型如下 void unlock();通过介绍以上三个函数使用互斥锁进行线程同步的大致思路差不多就能搞清楚了主要分为以下几步 找到多个线程操作的共享资源全局变量、堆内存、类成员变量等也可以称之为临界资源找到和共享资源有关的上下文代码也就是临界区下图中的黄色代码部分在临界区的上边调用互斥锁类的lock()方法在临界区的下边调用互斥锁的unlock()方法 线程同步的目的是让多线程按照顺序依次执行临界区代码这样做线程对共享资源的访问就从并行访问变为了线性访问访问效率降低了但是保证了数据的正确性。 注意 当线程对互斥锁对象加锁并且执行完临界区代码之后一定要使用这个线程对互斥锁解锁否则最终会造成线程的死锁。死锁之后当前应用程序中的所有线程都会被阻塞并且阻塞无法解除应用程序也无法继续运行。 1.2 线程同步 #include iostream #include thread #include mutex using namespace std;// 互斥锁 class Base { public:// count表示打印的个数void increment(int count) {for (int i 0; i count; i){mx.lock();num;cout current number: num endl;mx.unlock();this_thread::sleep_for(chrono::milliseconds(100));}}// count表示打印的个数void decrement(int count){for (int i 0; i count; i){mx.lock();--num;cout --current number: num endl;mx.unlock();this_thread::yield();}}private:// 此处为共享资源int num999;mutex mx; };int main() {Base b;thread t1(Base::increment,b,10);thread t2(Base::decrement, b, 10);t1.join();t2.join();return 0; }2.std::lock_guard类 lock_guard是C11新增的一个模板类使用这个类可以简化互斥锁lock()和unlock()的写法同时也更安全。这个模板类的定义和常用的构造函数原型如下 // 类的定义定义于头文件 mutex template class Mutex class lock_guard;// 常用构造函数 explicit lock_guard( mutex_type m );lock_guard在使用上面提供的这个构造函数构造对象时会自动锁定互斥量而在退出作用域后进行析构时就会自动解锁从而保证了互斥量的正确操作避免忘记unlock()操作而导致线程死锁。lock_guard使用了RAII技术就是在类构造函数中分配资源在析构函数中释放资源保证资源出了作用域就释放。 使用lock_guard对上面的例子进行修改代码如下 #include iostream #include thread #include mutex using namespace std;// 互斥锁 class Base { public:// count表示打印的个数void increment(int count) {for (int i 0; i count; i){{lock_guardmutex lock(mx);num;cout current number: num endl;}this_thread::sleep_for(chrono::milliseconds(100));}}// count表示打印的个数void decrement(int count){for (int i 0; i count; i){{lock_guardmutex lock(mx);--num;cout --current number: num endl;}this_thread::yield();}}private:// 此处为共享资源int num999;mutex mx; };int main() {Base b;thread t1(Base::increment,b,10);thread t2(Base::decrement, b, 10);t1.join();t2.join();return 0; }通过修改发现代码被精简了而且不用担心因为忘记解锁而造成程序的死锁需要根据实际情况选择最优的解决方案。 3.std::recursive_mutex类 递归互斥锁std::recursive_mutex允许同一线程多次获得互斥锁可以用来解决同一线程需要多次获取互斥量时死锁的问题在下面的例子中使用独占非递归互斥量会发生死锁 #include iostream #include thread #include mutex using namespace std;// 互斥锁 class Base { public:// count表示打印的个数void increment(int count){for (int i 0; i count; i){mx.lock();num;cout current number: num endl;mx.unlock();this_thread::sleep_for(chrono::milliseconds(100));}}// count表示打印的个数void decrement(int count){for (int i 0; i count; i){{lock_guardmutex lock(mx);// 发生死锁increment(2);--num;cout --current number: num endl;}this_thread::yield();}}private:// 此处为共享资源int num 999;mutex mx; //递归互斥锁 };int main() {Base b;thread t1(Base::increment, b, 10);thread t2(Base::decrement, b, 10);t1.join();t2.join();return 0; }上面的程序中执行了decrement( )中对incremen( )调用之后程序就会发生死锁在decrement( )中已经对互斥锁加锁了继续调用incremen( )函数已经得到互斥锁所有权的线程再次获取这个互斥锁的所有权就会造成死锁在C中程序会异常退出使用C库函数会导致这个互斥锁永远无法被解锁最终阻塞所有的线程。要解决这个死锁的问题一个简单的办法就是使用递归互斥锁std::recursive_mutex它允许一个线程多次获得互斥锁的所有权。修改之后的代码如下 #include iostream #include thread #include mutex using namespace std;// 互斥锁 class Base { public:// count表示打印的个数void increment(int count){for (int i 0; i count; i){mx.lock();num;cout current number: num endl;mx.unlock();this_thread::sleep_for(chrono::milliseconds(100));}}// count表示打印的个数void decrement(int count){for (int i 0; i count; i){{lock_guardrecursive_mutex lock(mx);increment(2);--num;cout --current number: num endl;}this_thread::yield();}}private:// 此处为共享资源int num 999;recursive_mutex mx; //递归互斥锁 };int main() {Base b;thread t1(Base::increment, b, 10);thread t2(Base::decrement, b, 10);t1.join();t2.join();return 0; }虽然递归互斥锁可以解决同一个互斥锁频繁获取互斥锁资源的问题但是还是建议少用主要原因如下 使用递归互斥锁的场景往往都是可以简化的使用递归互斥锁很容易放纵复杂逻辑的产生从而导致bug的产生递归互斥锁比非递归互斥锁效率要低一些。递归互斥锁虽然允许同一个线程多次获得同一个互斥锁的所有权但最大次数并未具体说明一旦超过一定的次数就会抛出std::system错误。 4.std::timed_mutex类 std::timed_mutex是超时独占互斥锁主要是在获取互斥锁资源时增加了超时等待功能因为不知道获取锁资源需要等待多长时间为了保证不一直等待下去设置了一个超时时长超时后线程就可以解除阻塞去做其他事情了。 std::timed_mutex比std::_mutex多了两个成员函数try_lock_for()和try_lock_until() void lock(); bool try_lock(); void unlock();// std::timed_mutex比std::_mutex多出的两个成员函数 template class Rep, class Periodbool try_lock_for (const chrono::durationRep,Period rel_time);template class Clock, class Durationbool try_lock_until (const chrono::time_pointClock,Duration abs_time);try_lock_for函数是当线程获取不到互斥锁资源的时候让线程阻塞一定的时间长度try_lock_until函数是当线程获取不到互斥锁资源的时候让线程阻塞到某一个指定的时间点关于两个函数的返回值当得到互斥锁的所有权之后函数会马上解除阻塞返回true如果阻塞的时长用完或者到达指定的时间点之后函数也会解除阻塞返回false 下面的示例程序中为大家演示了std::timed_mutex的使用 #include iostream #include thread #include mutex using namespace std;// 互斥锁 class Base { public:void work(){while (true) {// 锁未锁定--返回值为trueif (mx.try_lock_for(chrono::milliseconds(100))){cout countcount ok 线程ID this_thread::get_id() endl;// 模拟任务使用的时长this_thread::sleep_for(chrono::milliseconds(500));cout count count ok 线程ID this_thread::get_id()endl;// 互斥锁解锁mx.unlock();break;}else // 阻塞时间超时--返回false{// 模拟任务使用的时长this_thread::sleep_for(chrono::milliseconds(50));cout not ok 线程ID this_thread::get_id()endl;}}}private:int count 0; // 共享资源timed_mutex mx; // 超时独占互斥锁 };int main() {Base b;thread t1(Base::work, b);thread t2(Base::work, b);t1.join();t2.join();return 0; }**注意解除阻塞和解锁是两个不同的概念。**从上述的结果我们可以看出线程38332获得互斥锁的所有权try_lock_for函数超过阻塞时长返回false阻塞的另一个线程1864执行else中的任务由于这个是while(true)该线程1864重新被阻塞超过阻塞时长又重新执行else,直到线程38332解除互斥锁的所有权线程1864获得互斥锁的所有权执行接下来的过程。 四、线程同步之条件变量 1.生产者-消费者模型 条件变量通常用于生产者和消费者模型。生产者和消费者模型主要应用的场景是生产者和消费者的速度不匹配。 条件变量能够帮助我们解决某一类线程生产者或者消费者互斥锁能够帮助我们解决某一类线程因为多个线程访问共享资源导致的数据混乱问题。 生产者和消费者模型的组成 生产者线程-若干个 生产商品或者任务放入到任务队列中 任务队列满了生产者线程就阻塞不满就工作 通过一个生产者的条件变量控制生产者线程阻塞和非阻塞。消费者线程-若干个 读任务队列将任务或者数据取出 任务队列中有数据就有消费者没有数据就阻塞 通过一个消费者的条件变量控制消费者线程阻塞和非阻塞。任务队列-存储数据或者任务对应一块内存为了读写访问可以通过一个数据结构维护这块内存 数据结构的类型可以分为数组、链表也可以是STL容器queue\stack\list\vector) 注意推荐使用STL中的容器方便增加删除以及动态扩展等。 生产者消费者模型任务队列类的代码演示 #include queue #include iostream #include thread using namespace std;class WorkQueue { public:// 添加数据void put(const int task){workQueue.push(task); // 尾部添加数据cout 添加任务 task ,线程ID this_thread::get_id() endl;}// 删除数据void take(){int node workQueue.front(); // 取出头部数据workQueue.pop();cout 删除任务 node ,线程ID this_thread::get_id() endl;}// 判断任务队列是否满bool isFull(){return workQueue.size() maxSize;}// 判断任务队列是否空bool isEmpty(){return workQueue.size() 0;}// 当前任务队列的尺寸int workSize(){return workQueue.size();}private:int maxSize; //任务队列的容量std::queueint workQueue; // 任务队列的数据结构 };优点线程阻塞相比较线程空循环或者线程的重新创建来说效率更高资源消耗更少。 2.条件变量 condition_variable需要配合std::unique_lockstd::mutex进行wait操作也就是阻塞线程的操作。 condition_variable_any可以和任意带有lock()、unlock()语义的mutex搭配使用也就是说有四种 std::mutex独占的非递归互斥锁std::timed_mutex带超时的独占非递归互斥锁std::recursive_mutex不带超时功能的递归互斥锁std::recursive_timed_mutex带超时的递归互斥锁 条件变量通常用于生产者和消费者模型大致使用过程如下 拥有条件变量的线程获取互斥量循环检查某个条件如果条件不满足阻塞当前线程否则线程继续向下执行 产品的数量达到上限生产者阻塞否则生产者一直生产。。。 产品的数量为零消费者阻塞否则消费者一直消费。。。条件满足之后可以调用notify_one()或者notify_all()唤醒一个或者所有被阻塞的线程 由消费者唤醒被阻塞的生产者生产者解除阻塞继续生产。。。 由生产者唤醒被阻塞的消费者消费者解除阻塞继续消费。。。 2.1 成员函数 condition_variable的成员函数主要分为两部分线程等待阻塞函数 和线程通知唤醒函数这些函数被定义于头文件 condition_variable 等待函数 调用wait()函数的线程会被阻塞 // ① 调用该函数的线程直接被阻塞 void wait (unique_lockmutex lck); // ② 该函数的第二个参数是一个判断条件是一个返回值为布尔类型的函数 // 该参数可以传递一个有名函数的地址也可以直接指定一个匿名函数 // 表达式返回false当前线程被阻塞表达式返回true当前线程不会被阻塞继续向下执行 template class Predicate void wait (unique_lockmutex lck, Predicate pred);独占的互斥锁对象不能直接传递给wait()函数需要通过模板类unique_lock进行二次处理通过得到的对象仍然可以对独占的互斥锁对象做(lock、try_lock、try_lock_for、try_lock_until、unlock)操作使用起来更灵活。 如果线程被该函数阻塞这个线程会释放占有的互斥锁的所有权当阻塞解除之后这个线程会重新得到互斥锁的所有权继续向下执行。wait函数内部实现其目的是为了避免线程的死锁 wait_for()函数和wait()的功能是一样的只不过多了一个阻塞时长假设阻塞的线程没有被其他线程唤醒当阻塞时长用完之后线程就会自动解除阻塞继续向下执行。 template class Rep, class Period cv_status wait_for (unique_lockmutex lck,const chrono::durationRep,Period rel_time);template class Rep, class Period, class Predicate bool wait_for(unique_lockmutex lck,const chrono::durationRep,Period rel_time, Predicate pred);wait_until()函数和wait_for()的功能是一样的它是指定让线程阻塞到某一个时间点假设阻塞的线程没有被其他线程唤醒当到达指定的时间点之后线程就会自动解除阻塞继续向下执行。 template class Clock, class Duration cv_status wait_until (unique_lockmutex lck,const chrono::time_pointClock,Duration abs_time);template class Clock, class Duration, class Predicate bool wait_until (unique_lockmutex lck,const chrono::time_pointClock,Duration abs_time, Predicate pred);通知函数 void notify_one() noexcept; void notify_all() noexcept;notify_one()唤醒一个被当前条件变量阻塞的线程 notify_all()唤醒全部被当前条件变量阻塞的线程 生产者线程当任务队列满的时候阻塞生产者线程消费者线程当任务队列空的时候阻塞消费者线程。因此需要两个条件变量控制生产者线程和消费者线程阻塞。 #include queue #include iostream #include thread #include mutex #include condition_variable using namespace std;class WorkQueue { public:// 添加数据void put(const int task){unique_lockmutex locker(mx);// 如果任务队列满了就需要阻塞生产者线程while(workQueue.size()maxSize){// unique_lock对象类似lock_guard,管理mutex的加锁和解锁当线程访问到这个对象时未加锁则加锁反之阻塞// 如果locker析构了自动对mx管理的线程所有权进行解锁// 假设现在有线程A,B,C,线程A获得了互斥锁资源则线程B,C进行阻塞notFull.wait(locker); // 线程A在运行到wait的位置会先释放拥有的互斥锁资源解锁此时线程B,C从阻塞态变成就绪态线程A再阻塞所以不会出现死锁// 等到线程A从wait位置解除阻塞会跟其他线程再去抢互斥锁资源}workQueue.push(task); // 尾部添加数据cout 添加任务 task ,线程ID this_thread::get_id() endl;// 唤醒消费者notEmpty.notify_one();}// 删除数据void take(){unique_lockmutex locker(mx);// 如果任务队列空了就需要阻塞消费者线程while(workQueue.size()0){notEmpty.wait(locker);}int node workQueue.front(); // 取出头部数据workQueue.pop();cout 删除任务 node ,线程ID this_thread::get_id() endl;// 唤醒生产者notFull.notify_one();}// 判断任务队列是否满bool isFull(){// 任务队列的访问所以加锁std::lock_guardstd::mutex locker(mx);return workQueue.size() maxSize;}// 判断任务队列是否空bool isEmpty(){std::lock_guardstd::mutex locker(mx);return workQueue.size() 0;}// 当前任务队列的尺寸int workSize(){std::lock_guardstd::mutex locker(mx);return workQueue.size();}private:int maxSize100; //任务队列的容量std::queueint workQueue; // 任务队列的数据结构mutex mx; // 独占的互斥锁condition_variable notFull; // 控制生产者线程condition_variable notEmpty; // 控制消费者线程 };int main() {thread t1[5];thread t2[5];WorkQueue workQ;for (int i 0; i 5; i){t1[i] thread(WorkQueue::put, workQ, 100 i);t2[i] thread(WorkQueue::take, workQ);}for (int i 0; i 5; i){t1[i].join();t2[i].join();}return 0; }条件变量condition_variable类的wait()还有一个重载的方法可以接受一个条件这个条件也可以是一个返回值为布尔类型的函数条件变量会先检查判断这个条件是否满足如果满足条件布尔值为true则当前线程重新获得互斥锁的所有权结束阻塞继续向下执行如果不满足条件布尔值为false当前线程会释放互斥锁解锁同时被阻塞等待被唤醒。 优化后的代码 #include queue #include iostream #include thread #include mutex #include condition_variable using namespace std;class WorkQueue { public:// 添加数据void put(const int task){unique_lockmutex locker(mx);notFull.wait(locker, []() {return !(workQueue.size() maxSize);});workQueue.push(task); // 尾部添加数据cout 添加任务 task ,线程ID this_thread::get_id() endl;// 唤醒消费者notEmpty.notify_one();}// 删除数据void take(){unique_lockmutex locker(mx);notEmpty.wait(locker, []() {return !workQueue.size() 0;});int node workQueue.front(); // 取出头部数据workQueue.pop();cout 删除任务 node ,线程ID this_thread::get_id() endl;// 唤醒生产者notFull.notify_one();}// 判断任务队列是否满bool isFull(){// 任务队列的访问所以加锁std::lock_guardstd::mutex locker(mx);return workQueue.size() maxSize;}// 判断任务队列是否空bool isEmpty(){std::lock_guardstd::mutex locker(mx);return workQueue.size() 0;}// 当前任务队列的尺寸int workSize(){std::lock_guardstd::mutex locker(mx);return workQueue.size();}private:int maxSize100; //任务队列的容量std::queueint workQueue; // 任务队列的数据结构mutex mx; // 独占的互斥锁condition_variable notFull; // 控制生产者线程condition_variable notEmpty; // 控制消费者线程 };int main() {thread t1[50];thread t2[50];WorkQueue workQ;for (int i 0; i 50; i){t2[i] thread(WorkQueue::take, workQ);t1[i] thread(WorkQueue::put, workQ, 100 i);}for (int i 0; i 50; i){t1[i].join();t2[i].join();}return 0; }总结 本文详细介绍了C11多线程中thread线程类、mutex等互斥锁、以及线程同步和生产者消费者模型并提供了对应的示例本文主要是参考爱编程的大丙欢迎大家阅读大丙老师的文章本文作个人学习之用。
http://www.tj-hxxt.cn/news/232619.html

相关文章:

  • 珠海做网站wordpress教育类主题
  • 移动网站建设书籍推荐电子商务平台经营者所具备的功能
  • 企业网站建设制作设计哪家最专业小米官网页面
  • 网站的跳出率wordpress视屏
  • 网站设计简单吗网站建设方案平台
  • 如何在虚拟机中建设网站wordpress windows 伪静态
  • 昨天正常的网站突然显示建设中网站开发使用的工具
  • 建设部招标网 官方网站wordpress主题移动
  • 加强网站建设工作德州建设网站
  • 北京专业响应式网站建设怎么建设一个人自己网站
  • 腾讯云服务器搭建网站多少钱一个网站
  • 深圳网站建设开发哪家好免费制作视频的软件有哪些
  • 南昌公司建设网站费用怎样开通微信小程序卖东西
  • 做电容元器件的网站有哪些阿里巴巴国际站入驻费用
  • 公司网站建设服务公司网站制作公司网站建设
  • 校园二手市场网站建设百度手机应用市场
  • 网站vi设计公司咸阳网站建设公司哪家好
  • 做网站之前要备案是什么意思金融街做网站的公司
  • 广西城乡和建设厅网站免费申请etc
  • 网站设计优缺点网站上传照片失败
  • 网站设计模板html手机电商网站模板
  • 做网站找模版好吗中铁建设登录入口
  • 织梦做的网站怎么添加关键词asp与sql做网站
  • 中学生做的网站有哪些外贸站群
  • 网站建设技术网站建设wordpress shortcode 插件
  • 如何建立公司网站招标wordpress dz
  • 3 建设营销型网站流程图最好用的企业网站cms
  • 建站网址怎么改如何做某网站的移动客户端开发
  • 网站建设公司工作室代理记账一个月多少钱一个月
  • 网站建设需要什么内容域名解析到别人网站