Linux多线程服务端编程(笔记2)

    并发编程两种基本模型:messagepassing(分布式多机上的唯一实用模型),sharedmemory    

    线程同步四项原则:最低限度共享对象,使用高级的并发编程构件,只用非递归的互斥器和条件变量慎用读写锁不用信号量,不要用内核级同步原语更不要自己编写
    mutex:任何一个时刻最多只能有一个线程在此mutex划出的临界区内活动,使用mutex的原则:1)使用RAII(使用局部对象管理资源的技术通常称为“资源获取就是初始化”)封装mutex,非递归使用mutex,不手工调用lock()、unlock()一切交给栈上的MutexLockGuard的构造函数和析构函数负责(保证始终在同一个函数同一个scope对某个mutex加锁解锁),在每次构造MutexLockGuard对象时思考调用栈上已经持有的锁,不使用跨线程的mutex。通常我们用模板类std::unique_lock<>和std::lock_guard<>来lock和unlock一个mutex,这些类在构造函数中lock一个mutex,在析构函数中unlock它。因此,如果你用的是局部变量,你的mutex会在退出作用域时自动被unlock,unique_lock还提供unlock函数,使用者可以手动执行unlock。此外,unique_lock还可以设置超时。跨线程的mutex是不能由操作系统自动关闭的资源
    mutex有两种:递归(可重入,不用担心自己把自己锁死)和非递归(不可重入,同一线程不能重复对其加锁).同一线程可以重复对递归锁加锁但是不能重复对非递归锁加锁。pthread_mutex_t是非递归锁,可以显示的设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t设为递归锁。
gbd:threadapply all bt查看线程调用栈
    copyon write:Copy OnWrite(写时复制)使用了“引用计数”,会有一个变量用于保存引用的数量。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的引用计数为1或是0,此时,程序才会真正的Free这块从堆上分配的内存。在共享同一块内存的类发生内容改变时,才会发生写时复制
遍历容器是需要修改:推后修改,写时拷贝
    如果一个函数既有可能在已加锁的情况下使用,也有可能在未加锁的情况下使用,往往将这个函数拆成两个版本---加锁版本和不加锁版本(添加nolock后缀)。设计接口的时候,每个函数也被拆成两个函数,没有使用锁的函数是private或者protected类型,使用锁的的函数是public类型。作为对外接口的public函数只能调用无锁的私有变量函数,而不能互相调用。
    读写锁(例如Linux中的pthread_rwlock_t)提供了一个比互斥锁更高级别的并发访问。读写锁的实现往往是比互斥锁要复杂的,因此开销通常也大于互斥锁。写锁的优势往往展现在读操作很频繁,而写操作较少的情况下。如果一个线程先获得写锁,又获得读锁,则结果是无法预测的。需要注意的是,读锁是递归锁(即可重入),写锁是非递归锁(即不可重入)。尽量避免在同一个线程下混用读锁和写锁。
    递归锁是在非递归互斥锁加引用计数器来实现的。简单的说,在加锁前,先判断上一个加锁的线程和当前加锁的线程是否为同一个。如果是同一个线程,则仅仅引用计数器加1。如果不是的话,则引用计数器设为1,则记录当前线程号,并加锁。
    若一个函数既可能在已加锁的情况下调用,又可能在未加锁的情况下调用,那么就拆成两个函数:第一个为:跟原来函数同名,函数内部加锁并调用第二个函数;第二个函数后加上后缀WithLockHold不加锁把原来的函数体写在这里
    如果线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态。线程编译一定要加-lpthread
   __attribute__可以设置函数属性(FunctionAttribute)、变量属性(VariableAttribute)和类型属性(TypeAttribute),主要用于编译器优化。__attribute__书写特征是:__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数。
epoll类似于I/O多路复用技术,解决网络编程中收发数据时同步问题,Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关Epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD集合O(1)。
    类A和类B需要彼此互相引用,这样必然有一个类会先被定义,而另外一个类后被定义,这样在先被定义的类引用后被定义的类的时候,就导致了所谓的超前引用。两者的定义,至少有一方是使用指针,或两者都使用指针,但是决不能两者都定义实体对象。若A类需要使用B中的数据成员,B也要使用A的数据成员:classB;class(){};class B{};A::fun()将A中需要使用B的数据成员函数放在最后。
    doublechecked locking(DCL):双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
    spuriouswakeup(虚假唤醒):线程甚至在没有人向条件变量发送信号的情况下就有可能会被唤醒.notify或者notifyAll时,唤醒的并非满足了唤醒条件的线程。因为在notify或者notifyAll时不知道具体是唤醒的哪个线程,notify唤醒随机一个,notifyAll会唤醒所有对应的wait的线程,但是并非所有都是需要唤醒的,这个就是所谓的虚假唤醒了。
    pthread_cond_t(条件变量,管程):wait端:mutex保护,wait(),把判断布尔条件和wait放到while中。signal/broadcast端:mutex,修改布尔条件,signal是表示资源可用,broadcast表明状态变化。
    CountDownLatch其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。向CountDownLatch对象设置一个初始的数字作为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为0为止。有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。
    CountDownLatch: 一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行。 CyclicBarrier :N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”,是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。CountDownLatch是计数器,线程完成一个就记一个,就像报数一样,只不过是递减的.而CyclicBarrier更像一个水闸,线程执行就想水流,在水闸处都会堵住,等到水满(线程到齐)了,才开始泄流.
    read-copy-update:使用RCU保护的共享数据,读操作不需要获得任何锁就可以访问,不使用原子操作。写操作在访问前需要先复制一份副本,然后对副本进行修改,最后使用一个回调机制,在适当的时机把指向原来数据的指针重新指向新的被修改的数据
static_assert(exp,string)编译期对expression进行求值,当表达式为false(例如:断言失败)时,将string作为错误消息输出,exp中必须是常量表达式不能含变量.例:#defineMutexLockGuard(x) static_assert(false,"missing mutex guard varname")防止MutexLockGuard(mutex)遗漏变量名产生一个临时对象又马上销毁了,结果没有锁住临界区
    Arelease build uses optimizations. When you use optimizations tocreate a release build, the compiler will not produce symbolicdebugging information. The absence of symbolic debugging information,along with the fact that code is not generated for TRACE and ASSERTcalls, means that the size of your executable file is reduced andwill therefore be faster.
    ENOMEM没有内存可分配错误
    使用pthread库有一个好处是线程分析工具intelthread checker和valgrind-helgrind能识别,并依据happens-before关系分析程序有无datarace
    pthread_once_t的变量是一个控制变量。控制变量必须使用PTHREAD_ONCE_INIT宏静态地初始化。pthread_once(pthread_once_t*once_control, void (*init_routine) (void))//voidinit_routine()//函数首先检查控制变量,判断是否已经完成初始化,如果完成就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。如果在一个线程初始时,另外的线程调用pthread_once,则调用线程等待,直到那个现成完成初始话返回。
    singleton(单例模式):一个类Class只有一个实例存在,将生成对象函数私有化,然后公有函数配和 pthread_once()调用那个私有生成函数就可以只生成一个实例。
    pthread_key_create()用来创建线程私有数据。该函数从TSD池中分配一项,将其地址值赋给key供以后访问使用。第2个参数是一个销毁函数,它是可选的,可以为NULL,为NULL时,则系统调用默认的销毁函数进行相关的数据注销。如果不为空,则在线程退出时(调用pthread_exit()函数)时将以key锁关联的数据作为参数调用它,以释放分配的缓冲区,或是关闭文件流等。不论哪个线程调用了pthread_key_create(),所创建的key都是所有线程可以访问的,但各个线程可以根据自己的需要往key中填入不同的值,相当于提供了一个同名而不同值的全局变量(这个全局变量相对于拥有这个变量的线程来说)。注销一个TSD使用 pthread_key_delete()函数。该函数并不检查当前是否有线程正在使用该TSD,也不会调用清理函数(destructorfunction),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThread中,它还会将与之相关的线程数据项设置为NULL。在多线程程序中,经常要用全局变量来实现多个函数间的数据共享。由于数据空间是共享的,因此全局变量也为所有进程共有。但有时应用程序设计中必要提供线程私有的全局变量,这个变量仅在线程中有效,但却可以跨过多个函数访问。比如在程序里可能需要每个线程维护一个链表,而会使用相同的函数来操作这个链表,最简单的方法就是使用同名而不同变量地址的线程相关数据结构。函数pthread_setspecific()将pointer的值(不是锁指的内容)与key相关联。
    pthread_getspecific()将与key相关联的数据读出来。返回的数据类型都是void*,因此可以指向任何类型的数据
    内存可见性:个线程在获取锁之后会在自己的工作内存来操作共享变量,在工作内存中的副本回写到主内存,并且其它线程从主内存将变量同步回自己的工作内存之前,共享变量的改变对其它线程是不可见的。volatile保证可见性的原理是在每次访问变量时都会进行一次刷新,因此每次访问都是主内存中最新的版本
    Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
    线程等待分为两种:等待资源可用(select,poll,epoll_wait),等待进入临界区
    线程等待某个事件发生应该采用条件变量或IO事件回调不能用sleep轮询。
    assert()在releasebuild里是空语句
    包含一个cpp文件容易出现重复定义的错误,其原因是相当于把两个cpp文件连接起来
    shared_ptr实现copy-on-write:读端只需要拷贝shared_ptr增加shared_ptr计数就可以读取数据,写端检查shared_ptr的计数若为1则直接写数据,若不为1那么不能在原来的数据上修改,得创建一个副本,在副本上修改完了再替换原来数据。
    使用未经初始化的shared_ptr报错:Assertion`px != 0' failed.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值