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

南阳网站推广费用成都网络营销学校

南阳网站推广费用,成都网络营销学校,餐饮网站建设设计价格,ui设计是什么软件From: http://www.ibm.com/developerworks/cn/linux/1412_zhupx_thread/index.html 本文讲述了如何使用 C11 编写 Linux 下的多线程程序#xff0c;如何使用锁#xff0c;以及相关的注意事项#xff0c;还简述了 C11 引入的一些高级概念如 promise/future 等。 前言 在这个…From: http://www.ibm.com/developerworks/cn/linux/1412_zhupx_thread/index.html 本文讲述了如何使用 C11 编写 Linux 下的多线程程序如何使用锁以及相关的注意事项还简述了 C11 引入的一些高级概念如 promise/future 等。 前言 在这个多核时代如何充分利用每个 CPU 内核是一个绕不开的话题从需要为成千上万的用户同时提供服务的服务端应用程序到需要同时打开十几个页面每个页面都有几十上百个链接的 web 浏览器应用程序从保持着几 t 甚或几 p 的数据的数据库系统到手机上的一个有良好用户响应能力的 app为了充分利用每个 CPU 内核都会想到是否可以使用多线程技术。这里所说的“充分利用”包含了两个层面的意思一个是使用到所有的内核再一个是内核不空闲不让某个内核长时间处于空闲状态。在 C98 的时代C标准并没有包含多线程的支持人们只能直接调用操作系统提供的 SDK API 来编写多线程程序不同的操作系统提供的 SDK API 以及线程控制能力不尽相同到了 C11终于在标准之中加入了正式的多线程的支持从而我们可以使用标准形式的类来创建与执行线程也使得我们可以使用标准形式的锁、原子操作、线程本地存储 (TLS) 等来进行复杂的各种模式的多线程编程而且C11 还提供了一些高级概念比如 promise/futurepackaged_taskasync 等以简化某些模式的多线程编程。 多线程可以让我们的应用程序拥有更加出色的性能同时如果没有用好多线程又是比较容易出错的且难以查找错误所在甚至可以让人们觉得自己陷进了泥潭希望本文能够帮助您更好地使用 C11 来进行 Linux 下的多线程编程。 回页首 认识多线程 首先我们应该正确地认识线程。维基百科对线程的定义是线程是一个编排好的指令序列这个指令序列线程可以和其它的指令序列线程并行执行操作系统调度器将线程作为最小的 CPU 调度单元。在进行架构设计时我们应该多从操作系统线程调度的角度去考虑应用程序的线程安排而不仅仅是代码。 当只有一个 CPU 内核可供调度时多个线程的运行示意如下 图 1、单个 CPU 内核上的多个线程运行示意图 我们可以看到这时的多线程本质上是单个 CPU 的时间分片一个时间片运行一个线程的代码它可以支持并发处理但是不能说是真正的并行计算。 当有多个 CPU 或者多个内核可供调度时可以做到真正的并行计算多个线程的运行示意如下 图 2、双核 CPU 上的多个线程运行示意图 从上述两图我们可以直接得到使用多线程的一些常见场景 进程中的某个线程执行了一个阻塞操作时其它线程可以依然运行比如等待用户输入或者等待网络数据包的时候处理启动后台线程处理业务或者在一个游戏引擎中一个线程等待用户的交互动作输入另外一个线程在后台合成下一帧要画的图像或者播放背景音乐等。 将某个任务分解为小的可以并行进行的子任务让这些子任务在不同的 CPU 或者内核上同时进行计算然后汇总结果比如归并排序或者分段查找这样子来提高任务的执行速度。 需要注意一点因为单个 CPU 内核下多个线程并不是真正的并行有些问题比如 CPU 缓存不一致问题不一定能表现出来一旦这些代码被放到了多核或者多 CPU 的环境运行就很可能会出现“在开发测试环境一切没有问题到了实施现场就莫名其妙”的情况所以在进行多线程开发时开发与测试环境应该是多核或者多 CPU 的以避免出现这类情况。 回页首 C11 的线程类 std::thread C11 的标准类 std::thread 对线程进行了封装它的声明放在头文件 thread 中其中声明了线程类 thread, 线程标识符 id以及名字空间 this_thread按照 C11 规范这个头文件至少应该兼容如下内容 清单 1.例子 thread 头文件主要内容 namespace std{ struct thread{ // native_handle_type 是连接 thread 类和操作系统 SDK API 之间的桥梁。 typedef implementation-dependent native_handle_type; native_handle_type native_handle(); // struct id{ id() noexcept; // 可以由, 两个运算衍生出其它大小关系运算。 bool operator(thread::id x, thread::id y) noexcept; bool operator(thread::id x, thread::id y) noexcept; templateclass charT, class traits basic_ostreamcharT, traits operator(basic_ostreamcharT, traitsout, thread::id id); // 哈希函数 template class T struct hash; template struct hashthread::id; }; id get_id() const noexcept; // 构造与析构 thread() noexcept; templateclass F, class… Args explicit thread(Ff, Args… args); ~thread(); thread(const thread) delete; thread(thread) noexcept; thread operator( const thread) delete; thread operator(thread) noexcept; // void swap(thread) noexcept; bool joinable() const noexcept; void join(); void detach(); // 获取物理线程数目 static unsigned hardware_concurrency() noexcept; } namespace this_thead{ thread::id get_id(); void yield(); templateclass Clock, class Duration void sleep_until(const chrono::time_pointClock, Duration abs_time); templateclass Rep, class Period void sleep_for(const chromo::durationRep, Period rel_time); } } 和有些语言中定义的线程不同C11 所定义的线程是和操作系的线程是一一对应的也就是说我们生成的线程都是直接接受操作系统的调度的通过操作系统的相关命令比如 ps -M 命令是可以看到的一个进程所能创建的线程数目以及一个操作系统所能创建的总的线程数目等都由运行时操作系统限定。 native_handle_type 是连接 thread 类和操作系统 SDK API 之间的桥梁在 g(libstdc) for Linux 里面native_handle_type 其实就是 pthread 里面的 pthread_t 类型当 thread 类的功能不能满足我们的要求的时候比如改变某个线程的优先级可以通过 thread 类实例的 native_handle() 返回值作为参数来调用相关的 pthread 函数达到目的。thread::id 定义了在运行时操作系统内唯一能够标识该线程的标识符同时其值还能指示所标识的线程的状态其默认值 (thread::id()) 表示不存在可控的正在执行的线程即空线程比如调用 thead() 生成的没有指定入口函数的线程类实例当一个线程类实例的 get_id() 等于默认值的时候即 get_id() thread::id()表示这个线程类实例处于下述状态之一 尚未指定运行的任务 线程运行完毕 线程已经被转移 (move) 到另外一个线程类实例 线程已经被分离 (detached) 空线程 id 字符串表示形式依具体实现而定有些编译器为 0x0有些为一句语义解释。 有时候我们需要在线程执行代码里面对当前调用者线程进行操作针对这种情况C11 里面专门定义了一个名字空间 this_thread其中包括 get_id() 函数可用来获取当前调用者线程的 idyield() 函数可以用来将调用者线程跳出运行状态重新交给操作系统进行调度sleep_until 和 sleep_for 函数则可以让调用者线程休眠若干时间。get_id() 函数实际上是通过调用 pthread_self() 函数获得调用者线程的标识符而 yield() 函数则是通过调用操作系统 API sched_yield() 进行调度切换。 回页首 如何创建和结束一个线程 和 pthread_create 不同使用 thread 类创建线程可以使用一个函数作为入口也可以是其它的 Callable 对象而且可以给入口传入任意个数任意类型的参数 清单 2.例子 thread_run_func_var_args.cc int funcReturnInt(const char* fmt, ...){ va_list ap; va_start(ap, fmt); vprintf( fmt, ap ); va_end(ap); return 0xabcd; } void threadRunFunction(void){ thread* t new thread(funcReturnInt, %d%s\n, 100, \%); t-join(); delete t; } 我们也可以传入一个 Lambda 表达式作为入口比如 清单 3.例子 thread_run_lambda.cc void threadRunLambda(void){ int a 100, b 200; thread* t new thread( [](int ia, int ib){ cout (ia ib) endl; }, a, b ); t-join(); delete t; } 一个类的成员函数也可以作为线程入口 清单 4.例子 thread_run_member_func.cc struct God{ void create(const char* anything){ cout create anything endl; } }; void threadRunMemberFunction(void){ God god; thread* t new thread( God::create, god, the world ); t-join(); delete t; } 虽然 thread 类的初始化可以提供这么丰富和方便的形式其实现的底层依然是创建一个 pthread 线程并运行之有些实现甚至是直接调用 pthread_create 来创建。 创建一个线程之后我们还需要考虑一个问题该如何处理这个线程的结束一种方式是等待这个线程结束在一个合适的地方调用 thread 实例的 join() 方法调用者线程将会一直等待着目标线程的结束当目标线程结束之后调用者线程继续运行另一个方式是将这个线程分离由其自己结束通过调用 thread 实例的 detach() 方法将目标线程置于分离模式。一个线程的 join() 方法与 detach() 方法只能调用一次不能在调用了 join() 之后又调用 detach()也不能在调用 detach() 之后又调用 join()在调用了 join() 或者 detach() 之后该线程的 id 即被置为默认值空线程表示不能继续再对该线程作修改变化。如果没有调用 join() 或者 detach()那么在析构的时候该线程实例将会调用 std::terminate()这会导致整个进程退出所以如果没有特别需要一般都建议在生成子线程后调用其 join() 方法等待其退出这样子最起码知道这些子线程在什么时候已经确保结束。 在 C11 里面没有提供 kill 掉某个线程的能力只能被动地等待某个线程的自然结束如果我们要主动停止某个线程的话可以通过调用 Linux 操作系统提供的 pthread_kill 函数给目标线程发送信号来实现示例如下 清单 5.例子 thread_kill.cc static void on_signal_term(int sig){ cout on SIGTERM: this_thread::get_id() endl; pthread_exit(NULL); } void threadPosixKill(void){ signal(SIGTERM, on_signal_term); thread* t new thread( [](){ while(true){ counter; } }); pthread_t tid t-native_handle(); cout tid tid endl; // 确保子线程已经在运行。 this_thread::sleep_for( chrono::seconds(1) ); pthread_kill(tid, SIGTERM); t-join(); delete t; cout thread destroyed. endl; } 上述例子还可以用来给某个线程发送其它信号具体的 pthread_exit 函数调用的约定依赖于具体的操作系统的实现所以这个方法是依赖于具体的操作系统的而且因为在 C11 里面没有这方面的具体约定用这种方式也是依赖于 C编译器的具体实现的。 回页首 线程类 std::thread 的其它方法和特点 thread 类是一个特殊的类它不能被拷贝只能被转移或者互换这是符合线程的语义的不要忘记这里所说的线程是直接被操作系统调度的。线程的转移使用 move 函数示例如下 清单 6.例子 thread_move.cc void threadMove(void){ int a 1; thread t( [](int* pa){ for(;;){ *pa (*pa * 33) % 0x7fffffff; if ( ( (*pa) 30) 1) break; } }, a); thread t2 move(t); // 改为 t2 t 将不能编译。 t2.join(); cout a a endl; } 在这个例子中如果将 t2.join() 改为 t.join() 将会导致整个进程被结束因为忘记了调用 t2 也就是被转移的线程的 join() 方法从而导致整个进程被结束而 t 则因为已经被转移其 id 已被置空。 线程实例互换使用 swap 函数示例如下 清单 7.例子 thread_swap.cc void threadSwap(void){ int a 1; thread t( [](int* pa){ for(;;){ *pa (*pa * 33) % 0x7fffffff; if ( ( (*pa) 30) 1) break; } }, a); thread t2; cout before swap: t t.get_id() , t2 t2.get_id() endl; swap(t, t2); cout after swap : t t.get_id() , t2 t2.get_id() endl; t2.join(); cout a a endl; } 互换和转移很类似但是互换仅仅进行实例以 id 作标识的互换而转移则在进行实例标识的互换之前还进行了转移目的实例如下例的t2的清理如果 t2 是可聚合的joinable() 方法返回 true则调用 std::terminate()这会导致整个进程退出比如下面这个例子 清单 8.例子 thread_move_term.cc void threadMoveTerm(void){ int a 1; thread t( [](int* pa){ for(;;){ *pa (*pa * 33) % 0x7fffffff; if ( ( (*pa) 30) 1) break; } }, a); thread t2( [](){ int i 0; for(;;)i; } ); t2 move(t); // 将会导致 std::terminate() cout should not reach here endl; t2.join(); } 所以在进行线程实例转移的时候要注意判断目的实例的 id 是否为空值即 id()。 如果我们继承了 thread 类则还需要禁止拷贝构造函数、拷贝赋值函数以及赋值操作符重载函数等另外thread 类的析构函数并不是虚析构函数。示例如下 清单 9.例子 thread_inherit.cc class MyThread : public thread{ public: MyThread() noexcept : thread(){}; templatetypename Callable, typename... Args explicit MyThread(Callable func, Args... args) : thread( std::forwardCallable(func), std::forwardArgs(args)...){ } ~MyThread() { thread::~thread(); } // disable copy constructors MyThread( MyThread ) delete; MyThread( const MyThread ) delete; MyThread operator(const MyThread) delete; }; 因为 thread 类的析构函数不是虚析构函数在上例中需要避免出现下面这种情况 MyThread* tc new MyThread(...); ... thread* tp tc; ... delete tp; 这种情况会导致 MyThread 的析构函数没有被调用。 回页首 线程的调度 我们可以调用 this_thread::yield() 将当前调用者线程切换到重新等待调度但是不能对非调用者线程进行调度切换也不能让非调用者线程休眠这是操作系统调度器干的活。 清单 10.例子 thread_yield.cc void threadYield(void){ unsigned int procs thread::hardware_concurrency(), // 获取物理线程数目 i 0; thread* ta new thread( [](){ struct timeval t1, t2; gettimeofday(t1, NULL); for(int i 0, m 13; i COUNT; i, m * 17){ this_thread::yield(); } gettimeofday(t2, NULL); print_time(t1, t2, with yield); } ); thread** tb new thread*[ procs ]; for( i 0; i procs; i){ tb[i] new thread( [](){ struct timeval t1, t2; gettimeofday(t1, NULL); for(int i 0, m 13; i COUNT; i, m * 17){ do_nothing(); } gettimeofday(t2, NULL); print_time(t1, t2, without yield); }); } ta-join(); delete ta; for( i 0; i procs; i){ tb[i]-join(); delete tb[i]; }; delete tb; } ta 线程因为需要经常切换去重新等待调度它运行的时间要比 tb 要多比如在作者的机器上运行得到如下结果 $time ./a.out without yield elapse 0.050199s without yield elapse 0.051042s without yield elapse 0.05139s without yield elapse 0.048782s with yield elapse 1.63366s real 0m1.643s user 0m1.175s sys 0m0.611s ta 线程即使扣除系统调用运行时间 0.611s 之后它的运行时间也远大于没有进行切换的线程。 C11 没有提供调整线程的调度策略或者优先级的能力如果需要只能通过调用相关的 pthread 函数来进行需要的时候可以通过调用 thread 类实例的 native_handle() 方法或者操作系统 API pthread_self() 来获得 pthread 线程 id作为 pthread 函数的参数。 回页首 线程间的数据交互和数据争用 (Data Racing) 同一个进程内的多个线程之间多是免不了要有数据互相来往的队列和共享数据是实现多个线程之间的数据交互的常用方式封装好的队列使用起来相对来说不容易出错一些而共享数据则是最基本的也是较容易出错的因为它会产生数据争用的情况即有超过一个线程试图同时抢占某个资源比如对某块内存进行读写等如下例所示 清单 11.例子 thread_data_race.cc static void inc(int *p ){ for(int i 0; i COUNT; i){ (*p); } } void threadDataRacing(void){ int a 0; thread ta( inc, a); thread tb( inc, a); ta.join(); tb.join(); cout a a endl; } 这是简化了的极端情况我们可以一眼看出来这是两个线程在同时对a 这个内存地址进行写操作但是在实际工作中在代码的海洋中发现它并不一定容易。从表面看两个线程执行完之后最后的 a 值应该是 COUNT * 2但是实际上并非如此因为简单如 (*p)这样的操作并不是一个原子动作要解决这个问题对于简单的基本类型数据如字符、整型、指针等C提供了原子模版类 atomic而对于复杂的对象则提供了最常用的锁机制比如互斥类 mutex门锁 lock_guard唯一锁 unique_lock条件变量 condition_variable 等。 现在我们使用原子模版类 atomic 改造上述例子得到预期结果 清单 12.例子 thread_atomic.cc static void inc(atomicint *p ){ for(int i 0; i COUNT; i){ (*p); } } void threadDataRacing(void){ atomicint a(0) ; thread ta( inc, a); thread tb( inc, a); ta.join(); tb.join(); cout a a endl; } 我们也可以使用 lock_guardlock_guard 是一个范围锁本质是 RAII(Resource Acquire Is Initialization)在构建的时候自动加锁在析构的时候自动解锁这保证了每一次加锁都会得到解锁。即使是调用函数发生了异常在清理栈帧的时候也会调用它的析构函数得到解锁从而保证每次加锁都会解锁但是我们不能手工调用加锁方法或者解锁方法来进行更加精细的资源占用管理使用 lock_guard 示例如下 清单 13.例子 thread_lock_guard.cc static mutex g_mutex; static void inc(int *p ){ for(int i 0; i COUNT; i){ lock_guardmutex _(g_mutex); (*p); } } void threadLockGuard(void){ int a 0; thread ta( inc, a); thread tb( inc, a); ta.join(); tb.join(); cout a a endl; } 如果要支持手工加锁可以考虑使用 unique_lock 或者直接使用 mutex。unique_lock 也支持 RAII它也可以一次性将多个锁加锁如果使用 mutex 则直接调用 mutex 类的 lock, unlock, trylock 等方法进行更加精细的锁管理 清单 14.例子 thread_mutex.cc static mutex g_mutex; static void inc(int *p ){ thread_local int i; // TLS 变量 for(; i COUNT; i){ g_mutex.lock(); (*p); g_mutex.unlock(); } } void threadMutex(void){ int a 0; thread ta( inc, a); thread tb( inc, a); ta.join(); tb.join(); cout a a endl; } 在上例中我们还使用了线程本地存储 (TLS) 变量我们只需要在变量前面声明它是 thread_local 即可。TLS 变量在线程栈内分配线程栈只有在线程创建之后才生效在线程退出的时候销毁需要注意不同系统的线程栈的大小是不同的如果 TLS 变量占用空间比较大需要注意这个问题。TLS 变量一般不能跨线程其初始化在调用线程第一次使用这个变量时进行默认初始化为 0。 对于线程间的事件通知C11 提供了条件变量类 condition_variable可视为 pthread_cond_t 的封装使用条件变量可以让一个线程等待其它线程的通知 (waitwait_forwait_until)也可以给其它线程发送通知 (notify_onenotify_all)条件变量必须和锁配合使用在等待时因为有解锁和重新加锁所以在等待时必须使用可以手工解锁和加锁的锁比如 unique_lock而不能使用 lock_guard示例如下 清单 15.例子 thread_cond_var.cc #include thread #include iostream #include condition_variable using namespace std; mutex m; condition_variable cv; void threadCondVar(void){ # define THREAD_COUNT 10 thread** t new thread*[THREAD_COUNT]; int i; for(i 0; i THREAD_COUNT; i){ t[i] new thread( [](int index){ unique_lockmutex lck(m); cv.wait_for(lck, chrono::hours(1000)); cout index endl; }, i ); this_thread::sleep_for( chrono::milliseconds(50)); } for(i 0; i THREAD_COUNT; i){ lock_guardmutex _(m); cv.notify_one(); } for(i 0; i THREAD_COUNT; i){ t[i]-join(); delete t[i]; } delete t; } 从上例的运行结果也可以看到条件变量是不保证次序的即首先调用 wait 的不一定首先被唤醒。 回页首 几个高级概念 C11 提供了若干多线程编程的高级概念promise/future, packaged_task, async来简化多线程编程尤其是线程之间的数据交互比较简单的情况下让我们可以将注意力更多地放在业务处理上。 promise/future 可以用来在线程之间进行简单的数据交互而不需要考虑锁的问题线程 A 将数据保存在一个 promise 变量中另外一个线程 B 可以通过这个 promise 变量的 get_future() 获取其值当线程 A 尚未在 promise 变量中赋值时线程 B 也可以等待这个 promise 变量的赋值 清单 16.例子 thread_promise_future.cc promisestring val; static void threadPromiseFuture(){ thread ta([](){ futurestring fu val.get_future(); cout waiting promise-future endl; cout fu.get() endl; }); thread tb([](){ this_thread::sleep_for( chrono::milliseconds(100) ); val.set_value(promise is set); }); ta.join(); tb.join(); } 一个 future 变量只能调用一次 get()如果需要多次调用 get()可以使用 shared_future通过 promise/future 还可以在线程之间传递异常。 如果将一个 callable 对象和一个 promise 组合那就是 packaged_task它可以进一步简化操作 清单 17.例子 thread_packaged_task.cc static mutex g_mutex; static void threadPackagedTask(){ auto run [](int index){ { lock_guardmutex _(g_mutex); cout tasklet index endl; } this_thread::sleep_for( chrono::seconds(10) ); return index * 1000; }; packaged_taskint(int) pt1(run); packaged_taskint(int) pt2(run); thread t1([](){pt1(2);} ); thread t2([](){pt2(3);} ); int f1 pt1.get_future().get(); int f2 pt2.get_future().get(); cout task result f1 endl; cout task result f2 endl; t1.join(); t2.join(); } 我们还可以试图将一个 packaged_task 和一个线程组合那就是 async() 函数。使用 async() 函数启动执行代码返回一个 future 对象来保存代码返回值不需要我们显式地创建和销毁线程等而是由 C11 库的实现决定何时创建和销毁线程以及创建几个线程等示例如下 清单 18.例子 thread_async.cc static long do_sum(vectorlong *arr, size_t start, size_t count){ static mutex _m; long sum 0; for(size_t i 0; i count; i){ sum (*arr)[start i]; } { lock_guardmutex _(_m); cout thread this_thread::get_id() , count count , sum sum endl; } return sum; } static void threadAsync(){ # define COUNT 1000000 vectorlong data(COUNT); for(size_t i 0; i COUNT; i){ data[i] random() 0xff; } // vector futurelong result; size_t ptc thread::hardware_concurrency() * 2; for(size_t batch 0; batch ptc; batch){ size_t batch_each COUNT / ptc; if (batch ptc - 1){ batch_each COUNT - (COUNT / ptc * batch); } result.push_back(async(do_sum, data, batch * batch_each, batch_each)); } long total 0; for(size_t batch 0; batch ptc; batch){ total result[batch].get(); } cout total total endl; } 如果是在多核或者多 CPU 的环境上面运行上述例子仔细观察输出结果可能会发现有些线程 ID 是重复的这说明重复使用了线程也就是说通过使用 async() 还可达到一些线程池的功能。 回页首 几个需要注意的地方 thread 同时也是棉线、毛线、丝线等意思我想大家都能体会面对一团乱麻不知从何处查找头绪的感受不要忘了线程不是静态的它是不断变化的请想像一下面对一团会动态变化的乱麻的情景。所以使用多线程技术的首要准则是我们自己要十分清楚我们的线程在哪里线头线程入口和出口在哪里先安排好线程的运行注意不同线程的交叉点访问或者修改同一个资源包括内存、I/O 设备等尽量减少线程的交叉点要知道几条线堆在一起最怕的是互相打结。 当我们的确需要不同线程访问一个共同的资源时一般都需要进行加锁保护否则很可能会出现数据不一致的情况从而出现各种时现时不现的莫名其妙的问题加锁保护时有几个问题需要特别注意一是一个线程内连续多次调用非递归锁 (non-recursive lock) 的加锁动作这很可能会导致异常二是加锁的粒度三是出现死锁 (deadlock)多个线程互相等待对方释放锁导致这些线程全部处于罢工状态。 第一个问题只要根据场景调用合适的锁即可当我们可能会在某个线程内重复调用某个锁的加锁动作时我们应该使用递归锁 (recursive lock)在 C11 中可以根据需要来使用 recursive_mutex或者 recursive_timed_mutex。 第二个问题即锁的粒度原则上应该是粒度越小越好那意味着阻塞的时间越少效率更高比如一个数据库给一个数据行 (data row) 加锁当然比给一个表 (table) 加锁要高效但是同时复杂度也会越大越容易出错比如死锁等。 对于第三个问题我们需要先看下出现死锁的条件 资源互斥某个资源在某一时刻只能被一个线程持有 (hold) 吃着碗里的还看着锅里的持有一个以上的互斥资源的线程在等待被其它进程持有的互斥资源 不可抢占只有在某互斥资源的持有线程释放了该资源之后其它线程才能去持有该资源 环形等待有两个或者两个以上的线程各自持有某些互斥资源并且各自在等待其它线程所持有的互斥资源。 我们只要不让上述四个条件中的任意一个不成立即可。在设计的时候非常有必要先分析一下会否出现满足四个条件的情况特别是检查有无试图去同时保持两个或者两个以上的锁当我们发现试图去同时保持两个或者两个以上的锁的时候就需要特别警惕了。下面我们来看一个简化了的死锁的例子 清单 19.例子 thread_deadlock.cc static mutex g_mutex1, g_mutex2; static void inc1(int *p ){ for(int i 0; i COUNT; i){ g_mutex1.lock(); (*p); g_mutex2.lock(); // do something. g_mutex2.unlock(); g_mutex1.unlock(); } } static void inc2(int *p ){ for(int i 0; i COUNT; i){ g_mutex2.lock(); g_mutex1.lock(); (*p); g_mutex1.unlock(); // do other thing. g_mutex2.unlock(); } } void threadMutex(void){ int a 0; thread ta( inc1, a); thread tb( inc2, a); ta.join(); tb.join(); cout a a endl; } 在这个例子中g_mutex1 和 g_mutex2 都是互斥的资源任意时刻都只有一个线程可以持有加锁成功而且只有持有线程调用 unlock 释放锁资源的时候其它线程才能去持有满足条件 1 和 3线程 ta 持有了 g_mutex1 之后在释放 g_mutex1 之前试图去持有 g_mutex2而线程 tb 持有了 g_mutex2 之后在释放 g_mutex2 之前试图去持有 g_mutex1满足条件 2 和 4这种情况之下当线程 ta 试图去持有 g_mutex2 的时候如果 tb 正持有 g_mutex2 而试图去持有 g_mutex1 时就发生了死锁。在有些环境下可能要多次运行这个例子才出现死锁实际工作中这种偶现特性让查找问题变难。要破除这个死锁我们只要按如下代码所示破除条件 3 和 4 即可 清单 20.例子 thread_break_deadlock.cc static mutex g_mutex1, g_mutex2; static voi inc1(int *p ){ for(int i 0; i COUNT; i){ g_mutex1.lock(); (*p); g_mutex1.unlock(); g_mutex2.lock(); // do something. g_mutex2.unlock(); } } static void inc2(int *p ){ for(int i 0; i COUNT; i){ g_mutex2.lock(); // do other thing. g_mutex2.unlock(); g_mutex1.lock(); (*p); g_mutex1.unlock(); } } void threadMutex(void){ int a 0; thread ta( inc1, a); thread tb( inc2, a); ta.join(); tb.join(); cout a a endl; } 在一些复杂的并行编程场景如何避免死锁是一个很重要的话题在实践中当我们看到有两个锁嵌套加锁的时候就要特别提高警惕它极有可能满足了条件 2 或者 4。 回页首 结束语 上述例子在 CentOS 6.5g 4.8.1/g4.9 以及 clang 3.5 下面编译通过在编译的时候请注意下述几点 设置 -stdc11 链接的时候设置 -pthread 使用 g编译链接时设置 -Wl,--no-as-needed 传给链接器有些版本的 g需要这个设置 设置宏定义 -D_REENTRANT有些库函数是依赖于这个宏定义来确定是否使用多线程版本的。 具体可以参考本文所附的代码中的 Makefile 文件。 在用 gdb 调试多线程程序的时候可以输入命令 info threads 查看当前的线程列表通过命令 thread n 切换到第 n 个线程的上下文这里的 n 是 info threads 命令输出的线程索引数字例如如果要切换到第 2 个线程的上下文则输入命令 thread 2。 聪明地使用多线程拥抱多线程吧。 参考资料 学习 关于 thread 的定义可参考 http://en.wikipedia.org/wiki/Thread 和http://en.wikipedia.org/wiki/Thread_%28computing%29。 关于 C11 标准中 thread 以及其它相关类的声明可以参考草案N3242。 参考 Bjarne Stroustrup 的《The C Programming Language》第 4 版是 C11 的权威著作。 访问 developerWorks Linux 专区了解关于信息管理的更多信息获取技术文档、how-to 文章、培训、下载、产品信息以及其他资源。 讨论 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基并与其他 developerWorks 用户交流。
http://www.yutouwan.com/news/457961/

相关文章:

  • php网站后台怎么进备案 网站首页网址
  • 山东省住房和城乡建设厅服务网站沙井网站推广
  • python数据分析做网站免费建站的网站能做影视网站吗
  • 没有网站可以域名备案免费游戏网站模板
  • 搬瓦工可以做网站吗学校网站设计实验报告
  • 做网站的费用 可以抵扣吗wordpress怎么用七牛
  • 郑州建站网站的公司天河手机网站建设
  • php网站怎么做seo江苏省教育现代化建设水平监测网站
  • 郑州网络营销与网站推广企业员工培训课程
  • 福建龙祥建设集团公司网站朋友给我做网站
  • 内江市住房和城乡建设局网站电话wordpress怎么自己写源码吗
  • 制作响应式网站网页文章 在wordpress
  • 手机访问网站建设中wordpress yoast设置
  • php网站开发实战教程软件开发工程师报考条件
  • 做网站学费多少钱wordpress幻灯片插件 汉化
  • 哈尔滨的网站建设公司wordpress+win8
  • 做迅雷下载电影类网站会侵权么数字创意设计包括哪些行业
  • 温州网站建设科技有限公司常州 网站 推广
  • 想自己做网站吗安徽工程建设信用平台
  • 教育网站改造方案批量 网站标题
  • 成功网站管理系统竞价推广价格
  • 中英文网站建设报价wordpress站点管理
  • 凡科网网站系统企业门户网站建设 北京
  • 商河做网站多少钱培训机构网站建设推广
  • 做网站开发的经营范围中国发布网
  • wordpress下载资源站主题百度网页版浏览器网址
  • 网站建设基础实验1个人做电子商务网站备案
  • 房山网站建设优化seo专业做蛋糕视频网站
  • 网站加载很慢怎么办潮州网络推广
  • 西安网站建设公司都有哪些长沙做产检玛丽亚m网站