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

可以在什么网站做二建题目合肥网站建设设计外包

可以在什么网站做二建题目,合肥网站建设设计外包,网站页面设计成品下载,wordpress 官网前言 早期的C进行多线程编程#xff0c;往往需要根据不同的系统编写不同的代码#xff0c;但是在C11之后#xff0c;std中已经提供了多线程的支持#xff0c;所以对于不同操作系统只需要编写一次代码即可。 本文记录一次多线程开发过程中#xff0c;使用的C新特性#…前言 早期的C进行多线程编程往往需要根据不同的系统编写不同的代码但是在C11之后std中已经提供了多线程的支持所以对于不同操作系统只需要编写一次代码即可。 本文记录一次多线程开发过程中使用的C新特性指C11及之后引入的库或语法规则其中包括 std::functionstd::result_of / std::invoke_ofstd::bindstd::threadstd::packaged_taskstd::futurestd::promisestd::shared_futurestd::asyncstd::mutexstd::timed_mutexstd::recursive_mutexstd::recursive_timed_mutexstd::shared_mutexstd::lock_guardstd::unique_lockstd::shared_lockstd::try_lockstd::lockstd::condition_variable 注意若无特殊说明各种方法、类型在C11开始支持。 函数处理 C11之后提供了很多函数相关的模板类用于封装函数从而替代掉通过typedef的方式定义函数类型。 函数封装 std::function 该类型是一个模板类常常用于绑定一个函数或者函数对象重载了()的类。 对于该类型可以理解成对每个函数指针都使用using或typedef定义了相应的别名例如 void callback(int a, int b) {} using CallbackFunction void(*)(int, int); CallbackFunction cb1 callback; // using std::function has the same effects. std::functionvoid(int, int) cb2 callback; cb1(1, 2); cb2(1, 2);std::function可以用来传递回调函数相比于函数指针它更加灵活例如可以通过模板和模板参数包实现任意函数作为回调函数 templateclass T, class ...Args void test(std::functionT cb, Args ...args) {cb(args...); }这比使用函数指针作为类型参数要方便的多。 除此之外std::function在多线程中可以绑定到std::packaged_task上用于异步执行在std::packaged_task部分进行介绍。 下面给出几个特殊的实例 在匿名函数中如果需要实现递归函数通过将匿名函数绑定到std::functionauto factorial [](int n) {std::functionint(int) fac [](int n) { return (n 2) ? 1 : n * fac(n - 1); };// auto fac1 [](int n) { return (n 2) ? 1 : n * fac1(n - 1); }; // this doesnt work, cause it cannot get fac1 in itself. return fac(n); };上面的代码去掉外层匿名函数可以实现相同的效果这里只是展示在匿名函数内部如果需要使用递归则可以通过std::function来实现。同时使用auto推断是不可行的。可以将类成员函数绑定到std::function绑定时需要指定调用对象struct Foo {Foo(int num) : num_(num) {}Foo(const Foo other) default;void print_add(int i) { std::cout num_ i \n; }int num_; }; std::functionvoid(Foo, int) f_add_display Foo::print_add; Foo foo(314159); f_add_display(foo, 1); // same as foo.print_add(1);std::functionvoid(Foo, int) copy_f_add_display Foo::print_add; copy_f_add_display(foo, 1); // same as Foo(foo).print_add(1);上面的代码中需要注意第一个参数的类型如果第一个参数的类型为引用或者指针这是在原对象上进行操作而如果第一个参数类型为非引用或指针则是会通过拷贝构造函数构造一个对象绑定到参数上进行操作可以通过将复制构造器删除来证明将代码中的default改为delete此时无法通过编译。std::funciton可以绑定到类成员变量上用于获取成员变量的值std::functionint(Foo ) f_num Foo::num_; f_num(foo); // same as foo.num_, but f_num(foo) is a rvalue, rather than lvalue; std::functionint(Foo) copy_f_num Foo::num_; copy_f_num(foo); // same as Foo(foo).num_, but copy_f_num(foo) is a rvalue, rather than lvalue;上述代码同样需要注意第一个参数类型时引用或指针和不是引用或指针时的区别。同时如果返回值定义为非引用那么结果是一个右值当返回值定义为引用类型的时候其结果为一个左值。 获取函数返回值类型 std::result_of / std::invoke_of std::result_of在C17中已被标记为deprecated过时的在C20中已经被移除取而代之的是std::invoke_of。其功能是获取一个函数对象、函数等的返回类型。 std::result_of被std::invoke_of替代的原因是当传入的模板不是一个可执行类型时std::result_of的行为是未定义的而std::invoke_of则没有type类型除此之外std::result_of对于一些情况下出现一些奇怪的特点 返回值不能为一个函数或者数组但可以是指针数组作为参数会被转换为指针返回值和参数均不能是抽象类参数的cv限定符会被丢弃参数的类型不能是void std::result_of和std::invoke_of的使用方式略有不同前者按照F(Args...)的方式接收模板类型而后者按照F, Args...的方式接收参数例如 int test(int, int); std::result_ofdecltype(*test)(int, int)::type a; std::invoke_resultdecltype(*test), int, int::type b; // since C 17 // a and b are both int.std::result_of / std::invoke_of的使用场景往往是需要根据传入的函数来确定其返回值的时候使用例如在std::function中给出的回调例子如果需要根据回调的返回值来确定调用的返回值 // using result_of in template function, we must using to get the type. templateclass Func, class ...Args typename std::result_ofFunc(Args...)::type test1(Func cb, Args ...args) {using ReturnType typename std::result_ofFunc(Args...)::type;return cb(args...); }// same effects with decltype templateclass Func, class ...Args auto test2(Func cb, Args ...args) - decltype(cb(args...)) {return cb(args...); }// test1 and test2 are the same.// when parameter is std::funciton templateclass T, class ...Args auto test3(std::functionT cb, Args ...args) - typename std::result_ofstd::functionT(Args...)::type {return cb(args...); }// when parameter is std::funciton and using decltype templateclass T, class ...Args auto test4(std::functionT cb, Args ...args) - decltype(cb(args...)) {return cb(args...); }// test3 and test4 are the same.int x() { return 0; } int x1 test1(x); int x2 test2(x); int x3 test3(std::functionint(){x}); int x4 test4(std::functionint(){x});上面的代码中需要注意的是根据cppreference中的说法为了防止出现奇怪的特点我们会使用引用进行传递因此在第一个实现中使用万能引用对于上面的代码也给出了通过decltype利用返回值后置的特性实现。 decltype可以获取函数的返回值类型std::result_of / sdt::invoke_result也能获取返回值类型二者最大的区别在于前者需要传入一个值或者变量而后者需要传入类型。 函数绑定 std::bind std::bind用于给函数取别名或者重新定义函数的参数顺序以及默认值。 例如我们有如下的函数 void func(int a, int b, int c) {// do someting...std::cout a b c sdt::endl; } func(1, 2, 3);在某些情况下我们可能会需要传入默认的值那么函数可以写成如下的形式 void func(int a, int b, int c 3) {// do something...std::cout a b c sdt::endl; } func(1, 2); // this is ok, it is same with func(1, 2, 3);那么如果我们在使用的时候我们发现我们可能需要频繁的使用func(1, x, y)这个形式呢由于默认参数只能出现在非默认参数的后面那么此时要实现上述的功能则必须将参数的调用顺序改变。此时我们可能需要重新定义一个函数 void func(int x, int y) {func(1, x, y); } int x 0, y 0; // func(int, int) and func(int, int, int 3) are same, // so we need convert to make sure the call unambiguous. ((void(*)(int, int))func)(x, y);上面每次调用的时候需要转换当然也可以为后定义的func取一个新名字。上面的方法最大的问题在于我们只需要在某一个作用域频繁使用而我们却要为整个命名空间增加一个新的函数。有没有一种方法能够只在当前的作用域生成一个函数一旦退出当前的作用域那么生成的函数同时失效 通过std::bind实现 using namespace std::placeholders; // this if for placeholders: _1 and _2 auto newFunc std::bind(func, 1, _1, _2); int x 0, y 0; newFunc(x, y); // this is same with func(1, x, y);std::bind返回的是一个重载了()的类其行为根据绑定的参数决定占位符的个数代表调用的时候需要几个参数_1始终对应于第一个实参_2始终对应第二个实参而我们实际调用的是func(1, _1, _2);当然我们也可以通过std::bind()来改变参数的调用位置 auto newFunc std::bind(func, 1, _2, _1); // this will call func(1, _2, _1); newFunc(x, y); // this is same with func(1, y, x); because y is _2 and x is _1.由于std::bind()返回的是一个可调用对象当然可以将其绑定到一个std::function上 // the template parameter is decided by the return type of func and the num of placehoders. std::functionvoid(int, int) newFunc std::bind(func, 1, _1, _2); std::functionvoid(int, int, int) newFunc2 std::bind(func, _1, _2, _3);Lambda函数也可以实现上述的所有功能以其中一个为例 auto newFunc [] (int x, int y) {func(1, x, y); }; int x 0, y 0; newFunc(x, y); // same with func(1, x, y);注意当绑定的函数参数是引用的时候如果要使用引用传递需要用std::cref / std::ref进行封装不然其传递的是一个值例如 void modifyA(int a, int b, int c) {a -1;std::cout a b c std::endl; } int a 0, b 0, c 0; auto badFunc std::bind(modifyA, a, b, c); badFunc(); // after this a is still 0. auto goodFunc std::bind(modifyA, std::ref(a), b, c); goodFunc(); // after this a is -1.// implement by Lambda Function: auto anotherGoodFunc [a, b, c]() {modifyA(a, b, c); }; anotherGoodFunc();多线程中的函数处理 多线程 std::thread 在C11之前想要实现多线程的功能则必须使用操作系统提供的接口例如在Linux中的pthread这样的话无法做到跨平台对于不同的平台如果使用多线程往往需要编写两份不同的代码。而在C11到来之后其对不同的平台的线程库进行了封装只需要编写一份代码则可以在不同的平台上实现多线程的功能需要在运行的时候连接对应平台的线程库例如在Linux平台下则需要添加-lpthread的编译选项。 std::thread的使用非常简单只需要在构造的时候传入一个函数或者一个可调用对象重载了()操作符的类型与需要的参数 void func(int a, int b, int c) {} std::thread t1(func, 1, 2, 3); // t1 is another thread running func(1, 2, 3); // t2 is another thread running a lambda function. std::thread t2([]() { std::cout Hello Thread!\n; }); t1.join(); t2.join();上面的代码中使用到了std::thread::join其功能是让当前的进程等待某个进程执行结束后再继续执行当前的进程。例如在上面的代码中如果没有join语句那么当前的主线程可能执行结束后t1与t2依然没有执行完成此时由于主线程进程被回收对应的子线程也会被回收即t1、t2不能继续执行而如果调用了join则只有对应的线程执行完成后当前的线程才会继续执行在C20中提出了std::jthread即在析构的时候会自动调用join的线程其提出是为了更好的契合RAIIResources Acquisition is Initialization对于资源的申请在初始化的时候完成对于资源的释放由析构函数自动完成思想。 std::thread还有一个重要的函数detach其能够让线程脱离std::thread独立执行该函数往往用于实现某些后台进程的功能。一般情况下当std::thraed创建的对象被析构之后其管理的线程则不能继续执行。对于有些情况下我们需要让一个线程在后台执行而不需要通过std::thread对象获取相关信息那么我们可以让该线线程detach。这样的好处是可以让当前的线程及时的释放资源例如 void daemonFunc() {// do something... } void oneThreadFunc() {shared_ptrint integerP(new int[(int)8e6]); // big data here// do something...std::thread daemonT(daemonFunc);daemonT.detach();// if we use daemonT.join() rather than daemonT.detach() to make sure daemonT finish, // the integerP will be released after the daemonT thread. } // integerP is released after this function. std::thread t1(oneThreadFunc); t1.join();对于上面的的代码我们希望通过RAII来管理数据如果我们不通过detach那么我们必须通过daemonT.join()来保证daemonFunc退出后再析构daemonT对象如果这样的话integerP的释放则会被延迟到daemonT结束后但是上面的代码通过detach让线程脱离std::thread也能执行因此可以让integerP的释放不用等待该线程结束后。 不过需要注意的是在使用detach的时候我们需要确定不会使用到当前作用域申请的数据例如上面的代码中我们需要保证daemonFunc中不使用integerP所指向的数据。 多线程的函数绑定 std::packaged_task std::packaged_task的使用方法与std::function一模一样可以绑定任意一个可执行对象。唯一不同的是其提供了std::packaged_task::get_future方法该方法可以获取一个std::future对象用于获取函数的返回值如果函数有返回值的话。 异步获取返回值 std::future 上文提到的std::thread非常好用但是如果我们需要处理多线程函数的返回值呢 当然通过std::thread也是可以实现的共享一个变量当线程函数结束的时候将该变量设置为函数的返回值即可需要注意的是这样的实现往往需要借助条件变量或者锁来实现。当需要考虑锁的时候程序往往就会变得复杂起来我们的需求明明很简单获取多线程函数的返回值。能不能不用程序员管理锁即可实现该功能呢 对于一个有返回值的多线程函数我们只需要将其绑定到std:packaged_task上通过std::packaged_task::get_future获取到std::future对象之后就可以等待线程执行完成后获取到线程的返回值了这也是std::future的常见用法之一 int threadFunc() { return 0; } std::packaged_taskint() threadTask{threadFunc}; std::futureint result threadTask.get_future(); std::thread t{std::move(threadTask)}; std::cout result.get() std::endl; // this join() is needed, when the threadFunc returns, // because, the future can get but the thread may be stopping. t.join(); 在上面的代码中一定要记住调用join因为当函数返回的时候std::future便已经可以通过get获取值了但是此时的线程可能并未完全停止可能正在释放资源。 std::future也可以在一个单线程程序中使用其表现的行为类似于通过std::function对函数取了一个别名 int threadFunc() { return 0; } std::packaged_taskint() threadTask{threadFunc}; std::futureint result threadTask.get_future(); result.get();// same implementation: std::functionint() func(threadFun}; func();值传递 std::promise std::promise用于传递一个值可以是任意类型通过set_value进行设置通过get_future获取std::future对象后可以通过std::future::get方法获取传递的值该类支持的所有功能均可以通过条件变量实现但是使用std::promise在某些场景可以简化代码同时也不需要我们手动进行加锁等操作。 官网给出的一个例子 void accumulate(std::vectorint::iterator first,std::vectorint::iterator last,std::promiseint accumulate_promise) {int sum std::accumulate(first, last, 0);accumulate_promise.set_value(sum); // Notify future }void do_work(std::promisevoid barrier) {std::this_thread::sleep_for(std::chrono::seconds(1));barrier.set_value(); }int main() {std::vectorint numbers {1, 2, 3, 4, 5, 6};std::promiseint accumulate_promise;std::futureint accumulate_future accumulate_promise.get_future();std::thread work_thread(accumulate, numbers.begin(), numbers.end(),std::move(accumulate_promise));std::cout result accumulate_future.get() \n;work_thread.join(); // wait for thread completion }上面的例子中子线程对std::vector中的元素求和求和完成后通过std::promise进行传递在主线程中通过预先获取的future进行get操作。 std::promise也可以用来传递函数这里不给出例子。 当std::promise的模板类型为void的时候其往往可以作为一个Barrier来使用这里的例子在std::shared_future部分给出。 共享future std::shared_future std::shared_future是对std::future的封装其能够让多个线程通过get获取函数的返回值该类型的表现行为与std::conditon_variable类似。但是使用std::shared_future我们可以通过值传递的方式这里应该是浅拷贝让每个线程拥有自己的std::shared_future对象这样可以不用加锁也能实现条件变量的功能例如我们需要完成某些工作后才能让所有的其他进程启动 std::promisevoid ready; std::shared_futurevoid sf ready.get_future(); std::thread t1{[sf]() { sf.get(); std::cout Thread 1 has started\n; }}; std::thread t2{[sf]() { sf.wait(); std::cout Thread 2 has started\n; }}; std::thread t3{[sf]() { sf.wait(); std::cout Thread 3 has started\n; }}; // sf.wait() and sf.get() are the same here. std::this_thread::sleep_for(2000ms); std::cout Finish Some Preparation.\n; ready.set_value(); t1.join(); t2.join(); t3.join();上面的代码子线程中的输出一定是晚于主线程中的输出的。 异步启动与延迟启动 std::async 在前面介绍的使用std::packaged_task可以获取多线程函数的返回值但是上面的代码每一次都需要使用std::packaged_task进行一次封装在大多数情况下写起来比较复杂而std::async同样可以使用实现上述的功能而且其代码更加简洁 int threadFunc(int i) { i -1; return -1; } int x 0; // dont forget the std::ref to make it pass as a reference. std::futureint result std::async(threadFunc, std::ref(x)); std::cout result.get() std::endl;除此之外std::async还支持懒启动即只有调用std::future::wait或者std::future::get时多线程才回开始执行例如 void threadFunc() { std::cout Thread is running...\n; } std::futurevoid result std::async(std::launch::deferred, threadFunc); std::this_thread::sleep_for(2000ms); result.wait(); // threadFunc starts to run.锁 锁一直是多线程中必不可少的工具。C11之后标准库提供了各种各样的锁。 需要注意的是下面介绍的锁均是不公平锁被阻塞的进程在获取锁的时候不是按照阻塞的先后顺序获取锁。 互斥锁 std::mutex 互斥锁指的是任何时刻只有一个进程能够获得锁。其操作非常简单 std::mutex mu; mu.lock(); // do something. mu.unlock();上面的lock在不能获得锁的时候会被阻塞。也可以使用try_lock获取锁当获取失败的时候try_lock会返回false获取成功则是返回true std::mutex mu; if (mu.try_lock()) {// do something... mu.unlock(); } else {std::cerr failed to get mutex\n; }为try_lock设置时间 std::timed_mutex std::mute中的try_lock只会进行一次获取锁的操作在很多时候我们希望在一段时间内一直尝试获取锁而std::timed_mutex就能实现这项功能std::timed_mutex与std::mutex大同小异唯一不同的就是std::timed_mutex提供了std::timed_mutex::try_lock_for与std::timed_mutex::try_lock_until可以根据传入的参数来决定尝试获取锁的时间长短当在指定的时间内获取到锁时返回true否则返回false std::timed_mutex t_mu; if (t_mu.try_lock_for(2000ms)) {// ... do somethingt_mu.unlock(); } else {cerr Failed to get lock in 2000ms\n; }std::timed_mutex是std::mutex的扩充其当然也有lock与try_lock方法。 可重入互斥锁 std::recursive_mutex 可重入锁指的是同一个线程可以多次获取同一个锁。上文提到的std::mutex为非可重入锁当一个已经获取std::mutex的线程继续尝试lock时其会被一直阻塞此时发生了单个线程的死锁。而使用std::recursive_mutex可以保证获取了锁的线程能够重复获取该锁可重入锁一般用于需要递归的情况 std::recursive_mutex r_mu; int fabonacci(int n) {r_mu.lock();// using lock to make sure the cout will be outputting right.std::cout trying to get fabonacci( n )\n;if (n 1 || n 2) {r_mu.unlock();return 1;} else {int f1 fabonacci(n - 1);int f2 fabonacci(n - 2);std::cout n f1 f2 f1 f2 std::endl;r_mu.unlock();return f1 f2;} }每一次获取锁了之后在return之前都应该记得释放锁这很不符合RAII。 为可重入锁try_lock设置时间 std::recursive_timed_mutex 该锁与std::recursive_mutex非常类似两者区别与std::timed_mutex与std::mutex的区别相同std::recursive_temed_mutex可以通过try_lock_for与try_lock_until设置尝试的时间这里不在进行赘述。 共享锁、读写锁 std::shared_mutex 该类型时C17开始提供的。 std::shared_mutex提供两种获取锁的方法lock与lock_shared分别对应写锁与读锁 当一个线程获取写锁时其他的线程不能获取写锁或读锁当一个线程获取读锁时其他的线程不能获取写锁但是可以获取读锁。 该锁可以用于实现经典多线程问题读者和写者问题。 这里给出一个官网的例子 class ThreadSafeCounter { public:ThreadSafeCounter() default;// Multiple threads/readers can read the counters value at the same time.unsigned int get() const {std::shared_lock lock(mutex_);return value_;}// Only one thread/writer can increment/write the counters value.void increment() {std::unique_lock lock(mutex_);value_;}// Only one thread/writer can reset/write the counters value.void reset() {std::unique_lock lock(mutex_);value_ 0;}private:mutable std::shared_mutex mutex_;unsigned int value_{}; };int main() {ThreadSafeCounter counter;auto increment_and_print [counter]() {for (int i{}; i ! 3; i) {counter.increment();std::osyncstream(std::cout) std::this_thread::get_id() counter.get() \n; // this osyncstream is since C 20.}};std::thread thread1(increment_and_print);std::thread thread2(increment_and_print);thread1.join();thread2.join(); }上面的例子来自于官网的代码上面的ThreadSafeCounter类允许多个线程同时通过get获取值但是只能有一个线程进行increment同时在进行increment的时候其他线程不能通过get获取值。 上面的代码使用了std::shared_lock与std::unique_lock这一部分在后文中会进行介绍。 写线程饥饿 使用std::shared_mutex还有一个很重要的点需要注意写进程可能会出现饥饿。 考虑这样一种情况如果此时有个读线程已经获取了lock_shared此时写线程如果lock则会被阻塞而在写线程被阻塞的期间若有很多的新的读进程进行lock_shared进行读操作那么写线程必须要等待所有读线程完成操作后释放所有的读锁后才能获取到写锁这样的话写操作会被严重延迟。究其原因是C的所有锁均为不公平锁。 一种简单的方法是增加一个std::mutex不管是读操作还是写操作之前都需要获取该mutex同时在获取到读锁或写锁后释放掉该mutex这样就能保证读线程和写线程在获取所之前会先进行一次竞争操作便能够保证不破坏语义的情况下防止写线程饥饿但是这样会增加一定的开销。 通过RAII获取与释放锁 RAII指的是Resources Acquisition Is Initialization。说简单一点就是申请完资源后不需要进行手动释放系统自动释放资源。 例如通过智能指针能够在使用完资源后不需要使用free而是由系统析构函数自动释放。 在多线程的开发中在成功获取了锁之后还需要通过unlock操作将锁释放。这样不符合RAII的思想于是std中提供了一些能够在析构的时候自动释放锁的类型和方法。 std::lock_guard 该类型在构造的时候需要与任意一个具有lock与unlock方法的锁绑定对象一旦创建完成便获得了锁当对象析构的时候便释放了锁例如 std::mutex mu; void threadFunc() {std::lock_guard lg{mu};// acquire the lock// do something... } // there is no need to unlockstd::unique_lock std::lock_guard实现了RAII的特点但是其一旦获得了便不能通过unlock进行解锁。而std::unique_lock可以在获取锁后选择unlock或者lock释放与重新获取锁 std::mutex mu; void threadFunc() {std::unique_lock ul{mu};// acquire the lock// do something...ul.unlock(); // we can unlock if we need this lock no more.// do something...ul.lock(); // we can lock if we need this lock again. } // there is no need to unlock除此之外std::unique_lock的构造函数还能接收第二个参数其有三种类型 std::adop_lock传入该参数与不传表现一样即创建完成后便获得了锁std::try_lock传入该操作等价于在创建的时候进行一次try_lock操作由于std::unique_lock重载了bool运算法可以通过if(name)的方式来判断是否成功获取锁std::defer_lock传入该操作表示不获取锁但是后续可以通过lock获取锁该方法一般与std::lock一起使用以实现同时获取多个锁的效果。 std::shared_lock 该类型在C14开始支持。 该类型与std::unique_lock类似唯一不同的是其调用的是绑定的锁的lock_shared与unlock_shared方法。该类型常常与std::shared_mutex一起使用用于实现std::shared_mutex中共享锁读锁的RAII。 其同样在构造的时候能够传入第二个参数参数的类型与std::unique_lock的第二个参数类型相同。 奇怪的事std::shared_mutex是在C17才开始支持的而std::shared_lock在C14就已经支持了难道还有其他的支持lock_shared语义的对象我没有发现吗或者是官方打算让用户自己实现共享锁吗 同时获取多个锁 std::try_lock std::try_lock是一个函数其接受多个支持try_lock的锁对象且同时尝试获取多个锁若有任意一个的try_lock获取失败那么其余已经获取成功的锁会释放同时返回获取失败的索引下标从0开始计算。如果均try_lock成功那么该函数返回-1。 std::mutex m1, m2;int ret std::try_lock(m1, m2); if (ret -1) {// do something...m1.unlock();m2.unlock(); // dont forget release the locks. } else {// m1 and m2 are not got by this thread. }上面的代码需要手动释放锁我们也可以将std::mutex与std::unique_lock绑定以实现RAII std::mutex m1, m2;std::unique_lockstd::mutex ul1{m1, std::defer_lock}, ul2{m2, std::defer_lock}; int ret std::try_lock(m1, m2); if (ret -1) {// do something...// no need to release the locks. } else {// m1 and m2 are not got by this thread. }std::lock std::lock是同时获取多个锁当不能获取某个锁的时候会被阻塞同时保证之前已经获取的锁被释放。其使用方法与std::try_lock类似不同的是由于保证一定要同时获得锁之后才不会被阻塞所以该函数没有返回值。 std::mutex m1, m2;std::lock(m1, m2); // get m1 and m2 at same time. // do something... m1.unlock(); m2.unlock(); // dont forget release the locks.上面的代码需要手动释放锁我们也可以将std::mutex与std::unique_lock绑定以实现RAII std::mutex m1, m2;std::unique_lockstd::mutex ul1{m1, std::defer_lock}, ul2{m2, std::defer_lock}; std::lock(m1, m2); // get m1 and m2 at same time. // do something... // no need to release the locks.一个小问题 为什么没有实现同时获取多个共享锁std::lock_shared 实现同时获取多个锁的目的是为了防止因为申请与释放锁的顺序不当防止出现死锁。对于共享锁的lock_shared如果能够成功获取那么其他的线程依然能够通过lock_shared获取锁那么此时一定是不会发生死锁的所以完全没有必要实现一个std::lock_shared。 条件变量 std::condition_variable 条件变量需要与锁一起使用提供wait与notify这一对操作通过调用wait会阻塞当前的线程同时释放掉与条件变量绑定的锁而notify则可以唤醒被阻塞的线程线程一旦被唤醒其将会重新获取wait时释放的锁如果此时锁已经被其他线程获取那么则会进入阻塞状态直到成功获取锁。 使用条件变量的时候通常需要与某些变量相关联例如某些线程等待其他的线程初始化完成后这些进程才继续执行那么就可以通过条件变量来实现这里给出前面std::promise中的例子的条件变量实现 void accumulate(std::vectorint::iterator first,std::vectorint::iterator last) {std::unique_lockstd::mutex locker{mu};sum std::accumulate(first, last, 0);cv.notify_all(); // we usually use notify_all, cause we dont know who are waiting. }int main() {std::vectorint numbers {1, 2, 3, 4, 5, 6};std::thread work_thread(accumulate, numbers.begin(), numbers.end());std::unique_lockstd::mutex locker{mu};cv.wait(locker, []() { return sum ! -1;}); // wait until sum ! -1;std::cout sum std::endl;work_thread.join();return 0; }在使用条件变量的时候我们大多数条件下使用notify_all进行唤醒这是因为我们并不知道都有哪些进行被阻塞当唤醒所有被阻塞进程后不符合退出wait条件的进程会继续被阻塞同时需要注意的时wait会先判断是否满足停止条件而不是先阻塞然后唤醒后再进行判断。 又一个小问题 条件变量在wait之前需要先获取锁然后才可以进行wait操作那么在notify之前是否也一定需要获取锁呢 当然不是。在大多数的情况下进行notify之前需要获取锁是因为往往需要修改共享变量的值。而有些时候我们在notify之前可能并不需要修改共享变量的值那么此时则可以不用获取锁。也就是说锁并不是和notify关联的需不需要获取锁是由我们是否需要修改共享变量而决定的并不是由我们是否需要notify来决定只是大多数情况我们在notify之前需要修改共享变量这容易给人一种notify一定需要加锁才能进行的错觉。实际上一种简单的证明notify并不需要获得锁的方法是我们在修改完共享变量之后即可释放锁然后此时调用notify依然不会出现问题需要保证之后不再修改共享变量例如上面的代码修改成如下的代码依然没有问题 void accumulate(std::vectorint::iterator first,std::vectorint::iterator last) {std::unique_lockstd::mutex locker{mu};sum std::accumulate(first, last, 0);locker.unlock();cv.notify_all(); // we can notify without any locks. }int main() {std::vectorint numbers {1, 2, 3, 4, 5, 6};std::thread work_thread(accumulate, numbers.begin(), numbers.end());std::unique_lockstd::mutex locker{mu};cv.wait(locker, []() { return sum ! -1;}); // wait until sum ! -1;std::cout sum std::endl;work_thread.join();return 0; }后续 本文只是介绍一些现代C多线程常用的类和方法并没有介绍一些多线程编程的实例。后面会根据实际遇到的问题介绍一些开发中常见的多线程问题。 参考 std::function cppreference std::result_of / std::invoke_of cppreference std::bind cppreference std::thread cppreference std::packaged_task cppreference std::future cppreference std::promise cppreference std::shared_future cppreference std::async cppreference std::mutex cppreference std::timed_mutex cppreference std::recursive_mutex cppreference std::recursive_timed_mutex cppreference std::shared_mutex cppreference std::lock_guard cppreference std::unique_lock cppreference std::shared_lock cppreference std::condition_variable cppreference
http://www.yutouwan.com/news/498620/

相关文章:

  • 公司网站建设小知识傻瓜网页制作工具
  • 繁昌网站建设制作灯笼的手工做法步骤
  • 免费图纸网站咸阳网站建设有哪些
  • 百度seo优化网站自学app软件开发
  • 张家港网站建设早晨设计邮箱域名可以做网站吗
  • 用wordpress开发网站模板下载prozac
  • 绥芬河网站建设wordpress应用app主题
  • 个人网站用react做徐州网站制作企业
  • 永康市建设局网站为什么打不开上海网站设计知名乐云seo
  • 山东网站建设xywlcn小型企业网站建设旅游景点网论文
  • 网站建设与运营市场开拓方案不会代码可不可以做网站
  • wordpress建站吗做一个关于电影的网页设计
  • php网站开发答案网站模板制作与安装教程视频教程
  • 嘉兴网站seo外包买个域名多少钱一年
  • 广州外贸型网站加强网站和新媒体建设管理的意义
  • 做电路设计的兼职网站在线学习
  • 珠海建站服务徐州seo外包公司
  • yy头像在线制作网站长沙市旅游景点
  • 哈尔滨建设公司网站安卓app市场
  • 福州市网站建设有限公司新网站备案查询
  • 成都住建局官网报名被挤爆黑幕郑州seo关键词自然排名工具
  • 云南网站建设价格低工信部网站备案查询官网
  • 做网站的教程视频线上运营培训
  • 做一个企业网站要多少钱游戏论坛源码
  • 知识付费问答系统网站开发推广网站怎么建设和维护
  • 深圳网站小程序设计开发wordpress mce
  • 大理州城乡建设局官方网站网站死链怎么删除
  • 会做网站怎么赚钱中国建设工程协会网站
  • 深圳最好的网站开发公司北京建站管理系统价格
  • 京东网站建设目标免费查企业电话网站