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

怎么自己建设公司网站长春 网络公司

怎么自己建设公司网站,长春 网络公司,徐州市云龙区建设局网站,h5免费制作平台火蚁目录 1. C多线程 1.1 thread库 1.2 mutex库 1.3 RAII锁 1.4 atomicCAS 1.5 condition_variable 1.6 分别打印奇数和偶数 2. shared_ptr线程安全 2.1 库里面的shared_ptr使用 2.2 shared_ptr加锁代码 3. 单例模式线程安全 3.1 懒汉模式线程安全问题 3.2 懒汉模式最…目录 1. C多线程 1.1 thread库 1.2 mutex库 1.3 RAII锁 1.4 atomicCAS 1.5 condition_variable 1.6 分别打印奇数和偶数 2. shared_ptr线程安全 2.1 库里面的shared_ptr使用 2.2 shared_ptr加锁代码 3. 单例模式线程安全 3.1 懒汉模式线程安全问题 3.2 懒汉模式最终代码 3.3 懒汉模式的另一种写法 本篇完。 此篇建议学了Linux系统多线程部分再来看。 1. C多线程 在C11之前涉及到多线程问题都是和平台相关的比如windows和linux下各有自己的接口这使得代码的可移植性比较差。 C11中最重要的特性就是支持了多线程编程使得C在并行编程时不需要依赖第三方库而且在原子操作中还引入了原子类的概念。 1.1 thread库 查下文档 如图所示C11提供了thread库thread是一个类在使用的时候需要包含头文件pthread。 构造函数 默认构造函数thread()使用该构造函数创建的线程对象仅是创建对象线程并没有被创建也没有允许。thread(Fn fn, Args... args)这是一个万能引用模板。使用该构造函数时第一个参数是可调用对象可以是左值也可以是右值比如函数指针仿函数对象lambda表达式等等。后面的可变参数就是传给线程函数的实参是一个参数包也就是可变参数。thread(const thread) delete线程之间是禁止拷贝的。thread(thread x)移动构造函数。 成员函数 get_id用来获取当前线程的tid值。调用该函数通常都是当前线程但是当前的从线程从并没有自己的thread对象。 所以线程库由提供了一个命名空间该空间中有上图所示的几个函数可以通过命名空间来直接调用如 this_thread::get_id(); // 获取当前线程tid值哪个线程执行这条语句就返回哪个线程的tid值命名空间中的其他几个函数的用法也是这样。 yield调用该接口的线程会让其CPU让CPU调度其他线程。sleep_until调用该接口的线程会延时至一个确定的时间点。sleep_for调用该接口的线程会延时一个时间段如1s。 operator(thread t)移动赋值。 将一个线程对象赋值给另一个线程对象通常这么用 thread t1; // 仅创建对象不创建线程t1 thread(func); // t1线程函数并且执行 此时原本只创建的线程对象就有一个线程在跑了。 注意只能赋右值不能赋左值因为赋值运算符重载被禁掉了只有移动赋值。 join线程等待用来回收线程资源。一般主线程会调用该函数以t.join()的形式t就是需要被等待的线程对象此时主线程会阻塞在这里直到从线程运行结束。 如上面的多线程一样必须使用join否则线程资源不会回收而且如果从线程运行的时间比主线程长的话主线程会直接运行完并且回收所有资源导致从线程被强制结束。 joinable用来判断线程是否有效。 如果是以下任意情况则线程无效 采用无参构造函数构造的线程对象线程对象的状态已经转移给其他线程对象线程已经调用 join 或者 detach 结束 detach线程分离从线程结束后自动回收资源。 其他的就不介绍了用到的时候自行查文档即可。 要谨记thread是禁止拷贝的不允许拷贝构造以及赋值但是可以移动构造和移动赋值。 使用一下 #include iostream #include thread using namespace std;void Print(int n, int x) {for (int i 0; i n; i){cout this_thread::get_id() : i endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));x;} } int main() {int count 0;thread t1(Print, 10, ref(count));thread t2(Print, 10, ref(count));t1.join();t2.join();cout count endl;return 0; } 多次运行的结果不一样可能会出现像第一行一样的抢着打印的问题学了Linux多线程应该比较清楚下面就应该想到加锁了。 1.2 mutex库 如上图所示C11提供了mutex库mutex同样是一个类在使用的时候要包含头文件mutex。 构造函数 只有默认构造函数mutex()在创建互斥锁的时候不需要传任何参数。mutex(const mutex)delete禁止拷贝。 其他成员函数 lock()给临界区加锁加锁成功继续向下执行失败则阻塞等待。unlock()给临界区解锁。try_lock()给临界区尝试加锁加锁成功返回true加锁失败返回false。使用try_lock时如果申请失败则不阻塞跳过申请锁的部分执行非临界区代码。 来看伪代码 mutex mtx;if(mtx.try_lock()) {// 临界区代码// ...... } else {// 非临界区代码// ...... }mutex不能递归使用如下面伪代码所示 void Func(int n){lock(); // 加锁// 临界区代码// ......Func(n - 1); // 递归调用unlock(); // 解锁} 在递归中不能使用这样的锁会造成死锁。正确使用下 #include iostream #include thread #include mutex using namespace std;void Print(int n, int x, mutex mtx) {for (int i 0; i n; i){mtx.lock();cout this_thread::get_id() : i endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));x;mtx.unlock();} }int main() {mutex m;int count 0;thread t1(Print, 10, ref(count), ref(m));thread t2(Print, 10, ref(count), ref(m));t1.join();t2.join();cout count endl;return 0; } 后面再来看看怎么实现交错打印的效果再看看另一种用法lambda int main() {mutex mtx;int x 0;int n 10;thread t1([](){for (int i 0; i n; i){mtx.lock();cout this_thread::get_id() : i endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));x;mtx.unlock();}});thread t2([](){for (int i 0; i n; i){mtx.lock();cout this_thread::get_id() : i endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));x;mtx.unlock();}});t1.join();t2.join();cout x endl;return 0; } 上面代码的问题如果加锁解锁之间存在抛异常就死锁了这时就要用到RAII锁。 1.3 RAII锁 lock_guard是一个类采用了RAII方式来加锁解锁——将锁的生命周期和对象的生命周期绑定在一起。看下在Linux篇章写的代码把锁封装了 #pragma once #include iostream #include pthread.hclass Mutex { public:Mutex(pthread_mutex_t* mtx) :_pmtx(mtx){}void lock(){pthread_mutex_lock(_pmtx);std::cout 进行加锁成功 std::endl;}void unlock(){pthread_mutex_unlock(_pmtx);std::cout 进行解锁成功 std::endl;}~Mutex(){} protected:pthread_mutex_t* _pmtx; };class lockGuard // RAII风格的加锁方式 { public:lockGuard(pthread_mutex_t* mtx) // 因为不是全局的锁所以传进来初始化:_mtx(mtx){_mtx.lock();}~lockGuard(){_mtx.unlock();} protected:Mutex _mtx; }; 看库里的构造函数 lock_guard(mutex_type m)在创建这个对象的时候需要传入一把锁在构造函数中进行了加锁操作。lcok_guard(const lock_guard)delete该对象禁止拷贝因为互斥锁就不可以拷贝。 析构函数的作用就是将lock_guard对象的资源释放也就是进行解锁操作。 lock_guard只有构造函数和析构函数使用该类对象加锁时不需要我们去关心锁的释放但是它不能在对象生命周期结束之前主动解锁。 看一下unique_lock unique_lock也是一种RAII的加锁对象它和lock_guard的功能一样将锁的生命周期和对象的生命周期绑定在一起但是又有区别。 unique_lock(mutex_type m)这个和lock_guard的用法一样在构造函数中加锁。unique_lock(const unique_lock)delete同样禁止拷贝。 析构函数中和lock_guard一样也是进行解锁操作。 lock加锁。unlock解锁。try_lock尝试加锁。 在lock_guard中就没有这几个接口所以unique_lock可以在析构之前主动解锁主动解锁后仍然可以再主动加锁这一点lock_guard是不可以的。 try_lock_for尝试加锁一段时间时间到后自动解锁。try_lock_until尝试加锁到指定时间时间到来后自动解锁。 用法很多需要使用的时候可以结合库文档来使用。用一下lock_guardlambda的另一种用法 int main() {mutex mtx;int n 10;int m;cin m;vectorthread v(m);for (int i 0; i m; i){// 移动赋值给vector中线程对象v[i] thread([](){for (int i 0; i n; i){{lock_guardmutex lk(mtx);cout this_thread::get_id() : i endl;}std::this_thread::sleep_for(std::chrono::milliseconds(100));}});}for (auto t : v){t.join();}return 0; } 1.4 atomicCAS C11提供了原子操作我们知道线程不安全的主要原因就是访问某些公共资源的时候操作不是原子的如果让这些操作变成原子的后就不会存在线程安全问题了。 CAS原理 原子操作的原理就是CAS(compare and swap)。 CAS包含三个操作数内存位置(V)预期原值(A)和新值(B)。如果内存位置的值与预期原值相等那么处理器就会自定将该位置的值更新为新值。如果内存位置的值与预期原值不相等那么处理器不会做任何操作。 val是临界资源两个线程t1和t2同时对这个值进行加加操作每个线程都是将该值先拿到寄存器eax中。 线程将val值拿到寄存器eax中时同时将该值放入原值V中。在修改val值之前CPU会先判断eax中的值与原值V中的值是否相等如果相等则修改并且更新值如果不相等则不修改。 伪代码原理 while(1) {eax val; // 将val值取到寄存器eax中if(eax V) // 和原值相同可以修改{eax;V eax; // 修改原值val eax; // 修改val值break; // 访问结束跳出循环} }t1和t2虽然同时运行但是时间粒度划分到极小的时候CPU仍然是一个个在执行。 t1线程将val值拿到寄存器中并且赋原值经过判断发现和原值相同所以修改val值并放回到val的地址中。 此时t2线程被唤醒它将val值拿到寄存器中后与最开始的原值V相比发现不相同了所以就不进行修改而且继续循环知道寄存器中的值和原值相等才会改变。 原子操作虽然保证了线程安全但是另一个无法写的的线程会不停的循环而这也会占用一定的CPU资源。 CAS具体的原理有兴趣可以自行去了解深入了解后写在简历是加分项。 atomic也是一个类所以也有构造函数 经常使用的是atomic(T val)在创建的时候传入我们想要进行原子操作的变量。 int a atomic(1);此时变量a的操作就都成了原子操作了在多线程访问的时候可以保证线程安全。 成员函数 该类重载了–等运算符可以直接对变量进行操作。 看看没用atomic也没加锁的 int main() {mutex mtx;int x 0;int n 100000;int m 2;vectorthread v(m);for (int i 0; i m; i){// 移动赋值给vector中线程对象v[i] thread([](){for (int i 0; i n; i){x;}});}for (auto t : v){t.join();}cout x endl;return 0; } 两个线程互相抢着加就会出现有一个线程没加的情况看看加锁的 再看看用atomic的 和加锁效果一样。 1.5 condition_variable C11中同样也有条件变量用来实现线程的同步。 构造函数 在创建条件变量的时候不用传入参数同样是不允许被拷贝的。 其他成员函数 放入等待队列 wait(unique_lockmutex lock)该接口是将调用它的线程放入到条件变量的等待队列中。 wait(unique_lockmutex lck, Predicate pred)该接口和上面的作用一样只是多了一个pred参数当这个参数为true的话不放入等待队列为false时放入等待队列。 这里传入的锁是unique_lock而不是lock_guard。 这是因为当一个线程申请到锁进入临界区但是条件不满足被放入条件变量的等待队列中时会将申请到的锁释放。 lock_guard只能在对象生命周期结束时自动释放锁。 unique_lock可以在任意位置释放锁。 如果使用了lock_guard的话就无法在进入等待队列的时候释放锁了。 wait_for和wait_until都是等待指定时间一个是在等待队列中待指定时间另一个是在等待队列中带到固定的时间点后自定唤醒。 notify_one唤醒等待队列中的一个线程notify_all唤醒等待队列中的所有线程。 1.6 分别打印奇数和偶数 写一个程序支持两个线程交替打印一个打印奇数一个打印偶数。 分析 首先创建一个全局的变量val让两个线程去访问该变量并且进行加一操作。考虑到线程安全所以需要给对应的临界区加互斥锁mutex又是交替打印所以要使用条件变量condition_variable来控制顺序为了方便管理使用的锁是unique_lockmutex。 代码实现 int main() {int val 0;int n 10; // 打印的范围mutex mtx; // 创建互斥锁condition_variable cond; // 创建条件变量thread t1([](){while (val n){unique_lockmutex lock(mtx); // 加锁while (val % 2 0)// 判断是否是偶数{// 是偶数则放入等待队列中等待cond.wait(lock);}// 是奇数时打印cout thread1: this_thread::get_id() - val endl;cond.notify_one(); // 唤醒等待队列中的一个线程去打印偶数}});this_thread::sleep_for(chrono::microseconds(100));thread t2([](){while (val n){unique_lockmutex lock(mtx);while (val % 2 1){cond.wait(lock);}cout thread2: this_thread::get_id() - val endl;cond.notify_one();//唤醒等待队列中的一个线程去打印奇数}});t1.join();t2.join();return 0; } 上面代码两个线程执行的函数对象是lambda表达式所以创建线程对象时调用的是移动构造函数。 wait()的第二个参数是false的时候该线程被挂起到等待队列中是true的时候不挂起而且执行向下执行。第二个参数的false和true可以是返回值如代码就是使用的lambda表达式的返回值。 线程t1负责打印奇数t2负责打印偶数两个线程通过条件变量的控制交替打印。 还可以这么用 int main() {int val 0;int n 10; // 打印值的范围mutex mtx;condition_variable cond;bool ready true;// t1线程打印奇数thread t1([](){while (val n){{unique_lockmutex lock(mtx);cond.wait(lock, [ready](){return !ready; });cout thread1: this_thread::get_id() - val endl;val 1;ready true;cond.notify_one();}//this_thread::yield();this_thread::sleep_for(chrono::microseconds(10));}});// t2线程打印偶数thread t2([]() {while (val n){unique_lockmutex lock(mtx);cond.wait(lock, [ready](){return ready; });cout thread2: this_thread::get_id() - val endl;val 1;ready false;cond.notify_one();}});t1.join();t2.join();return 0; } 成功按照预期打印。 2. shared_ptr线程安全 智能指针复习从C语言到C_36(智能指针RAII)auto_ptrunique_ptrshared_ptrweak_ptr-CSDN博客 以前敲的shared_ptr加一个返回引用计数的接口 namespace rtx {templateclass Tclass shared_ptr{public:shared_ptr(T* ptr nullptr): _ptr(ptr), _pCount(new int(1)){}void Release(){if (--(*_pCount) 0) // 防止产生内存泄漏,和析构一样,写成一个函数{delete _ptr;delete _pCount;}}~shared_ptr(){Release();}shared_ptr(const shared_ptrT sp): _ptr(sp._ptr), _pCount(sp._pCount){(*_pCount);}shared_ptrT operator(const shared_ptrT sp){//if (this ! sp)if (_ptr ! sp._ptr) // 防止自己给自己赋值,注意不能比较this,类似s1 s2; 再来一次s1 s2;{ // 比较_pCount也行//if (--(*_pCount) 0) // 防止产生内存泄漏,和析构一样,写成一个函数//{// delete _ptr;// delete _pCount;//}Release();_ptr sp._ptr;_pCount sp._pCount;(*_pCount);}return *this;}T operator*(){return *_ptr;}T* operator-(){return _ptr;}int use_count(){return *_pCount;}protected:T* _ptr;int* _pCount;// 引用计数,有多线程安全问题,学了linux再讲,不能用静态成员}; } 先看看库里面的使用 int main() {std::shared_ptrdouble sp1(new double(7.77));std::shared_ptrdouble sp2(sp1);mutex mtx;vectorthread v(5);int n 100000;for (auto t : v){t thread([](){for (size_t i 0; i n; i){// 拷贝是线程安全的std::shared_ptrdouble sp(sp1);// 访问资源不是(*sp);}});}for (auto t : v){t.join();}cout *sp1 endl;cout sp1.use_count() endl;return 0; } 2.1 库里面的shared_ptr使用 能指针共同管理的动态内存空间是线程不安全的访问资源要自己加锁 再把std换成自己的命名空间 程序直接崩溃了因为有时候引用计数不对。 多个线程及主线程中的所有智能指针都共享引用计数又因为拷贝构造以及析构都不是原子的所以导致线程不安全问题。 解决办法和Linux中一样需要加锁 引用计数加加和减减都要加锁 放个代码 2.2 shared_ptr加锁代码 namespace rtx {templateclass Tclass shared_ptr{public:shared_ptr(T* ptr nullptr): _ptr(ptr), _pCount(new int(1)),_pMtx(new mutex){}shared_ptr(const shared_ptrT sp): _ptr(sp._ptr), _pCount(sp._pCount), _pMtx(sp._pMtx){_pMtx-lock();(*_pCount);_pMtx-unlock();}shared_ptrT operator(const shared_ptrT sp){//if (this ! sp)if (_ptr ! sp._ptr) // 防止自己给自己赋值,注意不能比较this,类似s1 s2; 再来一次s1 s2;{ // 比较_pCount也行//if (--(*_pCount) 0) // 防止产生内存泄漏,和析构一样,写成一个函数//{// delete _ptr;// delete _pCount;//}Release();_ptr sp._ptr;_pCount sp._pCount;_pMtx-lock();(*_pCount);_pMtx-unlock();}return *this;}void Release() // 防止产生内存泄漏,和析构一样,写成一个函数{bool flag false;_pMtx-lock();if (--(*_pCount) 0){delete _ptr;delete _pCount;flag true;}_pMtx-unlock();if (flag){delete _pMtx; // new出来的引用计数为0时要delete}}~shared_ptr(){Release();}T operator*(){return *_ptr;}T* operator-(){return _ptr;}int use_count(){return *_pCount;}protected:T* _ptr;int* _pCount;// 引用计数,有多线程安全问题,学了linux再讲,不能用静态成员mutex* _pMtx;}; }int main() {rtx::shared_ptrdouble sp1(new double(7.77));rtx::shared_ptrdouble sp2(sp1);mutex mtx;vectorthread v(7);int n 100000;for (auto t : v){t thread([](){for (size_t i 0; i n; i){// 拷贝是线程安全的rtx::shared_ptrdouble sp(sp1);// 访问资源不是mtx.lock();(*sp);mtx.unlock();}});}for (auto t : v){t.join();}cout *sp1 endl;cout sp1.use_count() endl;return 0; } 3. 单例模式线程安全 单例模式复习 从C语言到C_37(特殊类设计和C类型转换)单例模式-CSDN博客 3.1 懒汉模式线程安全问题 在C11之后饿汉模式是没有线程安全问题的(做了相关补丁)因为单例对象是在main函数之前就实例化的而多线程都是在main函数里面启动的。 但是懒汉模式是存在线程安全问题的当多个线程使用到单例对象时候在使用GetInstance()获取对象时用因为调度问题出现误判导致new多个单例对象。 这里给懒汉模式加个锁这里在getInstance这样加锁有没有什么问题 此时每个调用GetInstance()的线程都需要申请锁然后释放锁对锁的操作也是有开销的会有效率上的损失。 单例模式在单例一经创建以后就不会再创建了无论多少线程在访问已经创建的单例对象时都不会再创建线程就已经安全了。所以在单例对象创建以后根本没有必要再去申请锁和释放锁。 如果把加锁放在 if 里面呢这样是不行的因为第二次线程来的时候单例对象已经不是空的了所以锁就白加了。 此时就要双检查加锁 3.2 懒汉模式最终代码 class Singleton { public:static Singleton* GetInstance(){// 双检查加锁if (m_pInstance nullptr) // 保护第一次后后续不需要加锁{unique_lockmutex lock(_mtx); // 加锁防止new抛异常就用unique_lockif (m_pInstance nullptr) // 保护第一次时线程安全{m_pInstance new Singleton;}}return m_pInstance;}private:Singleton() // 构造函数{}Singleton(const Singleton s) delete; // 禁止拷贝Singleton operator(const Singleton s) delete; // 禁止赋值// 静态单例对象指针static Singleton* m_pInstance; // 单例对象指针static mutex _mtx; };Singleton* Singleton::m_pInstance nullptr; // 初始化为空 mutex Singleton::_mtx;int main() {Singleton* ps Singleton::GetInstance();//获取单例对象return 0; } 成功运行。 3.3 懒汉模式的另一种写法 放个代码 class Singleton { public:static Singleton* GetInstance(){// 局部的静态对象第一次调用时初始化// 在C11之前是不能保证线程安全的// C11之前局部静态对象的构造函数调用初始化并不能保证线程安全的原子性。// C11的时候修复了这个问题所以这种写法只能在支持C11以后的编译器上使用static Singleton _s;return _s;}private:// 构造函数私有Singleton(){};Singleton(Singleton const) delete;Singleton operator(Singleton const) delete; };int main() {Singleton::GetInstance();return 0; } C11之前局部静态对象的构造函数调用初始化并不能保证线程安全的原子性。 C11的时候修复了这个问题所以这种写法只能在支持C11以后的编译器上使用。 本篇完。 应该算是本专栏的最后一篇了泪目泪目。道祖且长行则将至想再深入学习C以后就靠自己拓展了。后一部分就是网络和Linux网络的内容了。
http://www.hkea.cn/news/14343933/

相关文章:

  • 白城做网站素材网站 源码
  • 国外移动网站设计创建一个购物网站
  • 怎么使用wordpress建站电子商务网站开发综合实训报告
  • 网站开发制作云盘浏览国外网站dns
  • 江门 网站设计xyz域名免费注册
  • 南京产品网站建设收费seo网站优化软件
  • 网站建设开发ppt模板建设农家书屋官方网站
  • 建设综合信息网站需要多少钱佛山网站建设公司怎么选
  • 做包装盒子的厂家哪个网站郑州一建
  • 建网站没有实体公司能建站吗企业管理咨询服务合同模板
  • 国内精品在线网站建设销售和营销的区别
  • 高级网站开发技术使用什么语言网上全网推广
  • 杭州公司建站模板电脑版和手机版网站怎么做
  • 南宁网站设计公司排名网站平台建设十大公司
  • 驾校网站模版徐州手机网站开发公司电话
  • 网站pv uv如何查看网站所用空间
  • 制作网站的要素新任上海市领导调整公示
  • 建设微信网站要多少钱如何加入小说网站做打字员
  • 中国建造师官方网站中国服务外包网网址
  • 有自己的网站怎么做淘宝客网站开发钱包
  • 网页新建站点网站设计与开发期末考试题
  • xml网站地图在线生成工具wordpress标签tag链接静态化
  • 口碑好的秦皇岛网站建设价格许昌做网站公司汉狮价格
  • 如何建设高校网站网站架构教程
  • 怎样做自己可以发布消息的网站东莞市企业信息查询网
  • 网站建设中页面成都住建局官网住建蓉e办
  • 永倍达电商平台专业网站优化公司报价
  • 专为男人做的网站专业网站开发报价
  • 网站登录注册页面模板php教育网站开发
  • 中国东凤网站制作佛山市做网站的