linux驱动创建新的线程运行,使用 C++11 编写 Linux 多线程程序(2)

ta 线程即使扣除系统调用运行时间 0.611s 之后,它的运行时间也远大于没有进行切换的线程。

C++11 没有提供调整线程的调度策略或者优先级的能力,如果需要,只能通过调用相关的 pthread 函数来进行,需要的时候,可以通过调用 thread 类实例的 native_handle() 方法或者操作系统 API pthread_self() 来获得 pthread 线程 id,作为 pthread 函数的参数。

同一个进程内的多个线程之间多是免不了要有数据互相来往的,队列和共享数据是实现多个线程之间的数据交互的常用方式,封装好的队列使用起来相对来说不容易出错一些,而共享数据则是最基本的也是较容易出错的,因为它会产生数据争用的情况,即有超过一个线程试图同时抢占某个资源,比如对某块内存进行读写等,如下例所示:

这是简化了的极端情况,我们可以一眼看出来这是两个线程在同时对&a 这个内存地址进行写操作,但是在实际工作中,在代码的海洋中发现它并不一定容易。从表面看,两个线程执行完之后,最后的 a 值应该是 COUNT * 2,但是实际上并非如此,因为简单如 (*p)++这样的操作并不是一个原子动作,要解决这个问题,对于简单的基本类型数据如字符、整型、指针等,C++提供了原子模版类 atomic,而对于复杂的对象,则提供了最常用的锁机制,比如互斥类 mutex,门锁 lock_guard,唯一锁 unique_lock,条件变量 condition_variable 等。

现在我们使用原子模版类 atomic 改造上述例子得到预期结果:

//l0和l1都已获取到,因为unique_lock在释构时会释放l0,所以要调用release()函数,不让它释放l0锁。如果foo没有一个虚拟析构函数,编译器只会生成一个对ibar隐式空析构函数的调用,而foo的构析函数并没有被调用,因此会造成内存漏泄。l 不手工调用 lock() 和 unlock() 函数,一切交给栈上的 guard 对象的构造和析构函数负责,guard 对象的生命期正好等于临界区(分析对象在什么时候析构是 c++ 程序员的基本功)。

如果要支持手工加锁,可以考虑使用 unique_lock 或者直接使用 mutex。unique_lock 也支持 RAII,它也可以一次性将多个锁加锁;如果使用 mutex 则直接调用 mutex 类的 lock, unlock, trylock 等方法进行更加精细的锁管理:

在上例中,我们还使用了线程本地存储 (TLS) 变量,我们只需要在变量前面声明它是 thread_local 即可。TLS 变量程栈内分配,线程栈只有程创建之后才生效,程退出的时候销毁,需要注意不同系统的线程栈的大小是不同的,如果 TLS 变量占用空间比较大,需要注意这个问题。TLS 变量一般不能跨线程,其初始化在调用线程第一次使用这个变量时进行,默认初始化为 0。

ae56572ee5744439912660cdef2b1886.png

一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。线程2 bs_rcv_pic_thread使用条件变量阻塞,线程1 ms_snd_pic_thread在recvfrom处阻塞等待udp数据包,所以程1不能给互斥锁上锁,当接收到数据并写完文件后,向条件变量发出信号,以唤醒等待线程2,bs_rcv_pic_thread开始工作。递归锁与条件变量在一起使用的时候要特别小心,若线程a加了递归锁没有完全释放就进入了条件变量,等待唤醒。

#include

#include

#include

condition_variable cv;

# define THREAD_COUNT 10

从上例的运行结果也可以看到,条件变量是不保证次序的,即首先调用 wait 的不一定首先被唤醒。

C++11 提供了若干多线程编程的高级概念:promise/future, packaged_task, async,来简化多线程编程,尤其是线程之间的数据交互比较简单的情况下,让我们可以将注意力更多地放在业务处理上。

该方法用来通知那些可能在等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选其中一个wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。答:先生成一个对象obj,在一个全局map里put(id,obj)存放起来,再用synchronized获取obj锁,再调用obj.wait()让当前线程处于等待状态,然后另一消息线程等到服务端结果来了后,再map.get(id)找到obj,再用synchronized获取obj锁,再调用obj.notifyall()唤醒前面处于等待状态的线程。内置变量griddim,对于所有线程块来说,这个变量是一个常数,用来保存线程格每一维的大小,即每个线程格中线程块的数量。

cout<

而在某些情况下,可能需要唤醒多个线程(比如一个写入者唤醒多个读者线程),此时可以调用pthread_cond_broadcast唤醒阻塞在相应条件变量上的所有线程。其中第一个参数用来保存线程信息,第二个参数指新线程的运行属性,可以设置为null,第三个参数为自定义的线程函数,第四个参数就是线程函数需要用到的参数,一般如果要传递多个参数,可以设置为结构体(struct)类型,这里我们使用int类型的变量。如果仅仅是使用互斥量来同步,则就可能出现前面的忙等待(即使调用sched_yield也只是延迟了而已),而使用条件变量后,则线程就可以阻塞休眠了,直到需要的条件发生(在这里就是nready.nready的值非零),而改变这个条件的线程也是通过条件变量(以条件变量做为参数调用pthread_cond_signal或pthread_cond_broadcast)来通知阻塞在相应条件变量的线程。

3.实现callable接口,实现call方法,futuretask包装callable接口,futuretask对象创建thread对象,常用语异步操作,建议使用匿名内部类,方便阅读和使用。实现callable接口,实现call方法,futuretask包装callable接口,futuretask对象创建thread对象,常用语异步操作,建议使用匿名内部类,方便阅读和使用。callable接口是jdk5后新增的接口,而且不是runnable的子接口,所以callable对象不能直接作为thread的target。

cout<

cout<

我们还可以试图将一个 packaged_task 和一个线程组合,那就是 async() 函数。使用 async() 函数启动执行代码,返回一个 future 对象来保存代码返回值,不需要我们显式地创建和销毁线程等,而是由 C++11 库的实现决定何时创建和销毁线程,以及创建几个线程等,示例如下:

<

# define COUNT 1000000

1449409345_0.gif

如果是在多核或者多 CPU 的环境上面运行上述例子,仔细观察输出结果,可能会发现有些线程 ID 是重复的,这说明重复使用了线程,也就是说,通过使用 async() 还可达到一些线程池的功能。

thread 同时也是棉线、毛线、丝线等意思,我想大家都能体会面对一团乱麻不知从何处查找头绪的感受,不要忘了,线程不是静态的,它是不断变化的,请想像一下面对一团会动态变化的乱麻的情景。所以,使用多线程技术的首要准则是我们自己要十分清楚我们的线程在哪里?线头(线程入口和出口)在哪里?先安排好线程的运行,注意不同线程的交叉点(访问或者修改同一个资源,包括内存、I/O 设备等),尽量减少线程的交叉点,要知道几条线堆在一起最怕的是互相打结。

wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。线程a出现异常并释放锁,线程b进入方法正常打印,实验的结论就 出现异常的锁被自动释放。

第一个问题只要根据场景调用合适的锁即可,当我们可能会在某个线程内重复调用某个锁的加锁动作时,我们应该使用递归锁 (recursive lock),在 C++11 中,可以根据需要来使用 recursive_mutex,或者 recursive_timed_mutex。

第二个问题,即锁的粒度,原则上应该是粒度越小越好,那意味着阻塞的时间越少,效率更高,比如一个,给一个数据行 (data row) 加锁当然比给一个表 (table) 加锁要高效,但是同时复杂度也会越大,越容易出错,比如死锁等。

对于第三个问题我们需要先看下出现死锁的条件:

资源互斥,某个资源在某一时刻只能被一个线程持有 (hold);

吃着碗里的还看着锅里的,持有一个以上的互斥资源的线程在等待被其它进程持有的互斥资源;

不可抢占,只有在某互斥资源的持有线程释放了该资源之后,其它线程才能去持有该资源;

环形等待,有两个或者两个以上的线程各自持有某些互斥资源,并且各自在等待其它线程所持有的互斥资源。

我们只要不让上述四个条件中的任意一个不成立即可。在设计的时候,非常有必要先分析一下会否出现满足四个条件的情况,特别是检查有无试图去同时保持两个或者两个以上的锁,当我们发现试图去同时保持两个或者两个以上的锁的时候,就需要特别警惕了。下面我们来看一个简化了的死锁的例子:

// do something.

// do other thing.

在这个例子中,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 即可:

// do something.

// do other thing.

在一些复杂的并行编程场景,如何避免死锁是一个很重要的话题,在实践中,当我们看到有两个锁嵌套加锁的时候就要特别提高警惕,它极有可能满足了条件 2 或者 4。

若通过以上更新驱动的方法未通过检测,请继续下述操作删除驱动。若通过以上更新驱动的方法未通过检测,请继续下述操作。直接yum安装找不到该包,clang不在yum源里面,通过下面方式进行:。

具体可以参考本文所附的代码中的 Makefile 文件。

在用 gdb 调试多线程程序的时候,可以输入命令 info threads 查看当前的线程列表,通过命令 thread n 切换到第 n 个线程的上下文,这里的 n 是 info threads 命令输出的线程索引数字,例如,如果要切换到第 2 个线程的上下文,则输入命令 thread 2posix 线程池。

聪明地使用多线程,拥抱多线程吧。

本文来自电脑杂谈,转载请注明本文网址:

http://www.pc-fly.com/a/jisuanjixue/article-100741-2.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值