美文网首页程序员
linux c/c++多线程看了肯定懂

linux c/c++多线程看了肯定懂

作者: mfdalf | 来源:发表于2020-10-02 16:41 被阅读0次
    加粗样式

    正文

    1 线程基础知识
    1.1 线程编译
    gcc -o pthread -lpthread pthread.c
    1.2 线程和进程的区别

    进程 线程 作用
    fork() pthread_create() 创建
    exit() pthread_exit() 退出
    wait()/waitpid() pthread_join() 回收资源
    kill() pthread _cancel() 杀死进程、线程
    getpid() pthread_self() 获取当前进程/线程号
    exec pthread_detach 子线程/进程脱离主线程/进程

    1.3 线程状态转换

    在这里插入图片描述
    2 线程同步
    大部分项目都会用到线程同步,而且下面的3种方法都会用到.
    为什么要线程同步?
    1 ) 防止多个线程共同访问一个全局变量或者函数产生错误. 多线程访问多一个函数的局部变量会产生错误吗?答案是不会,因为每个线程有自己的栈,局部变量保存在自己的栈中.https://www.cnblogs.com/binghe001/p/12808419.html
    2 ) 线程时序控制
    2.1 互斥
    使用方法:锁.作用:防止多个线程访问互相影响,作用与下面2个不同.
    pthread_mutex_lock(&rd);
    readCount++;
    pthread_mutex_unlock(&rd)
    

    具体实例网上很多.
    2.2 信号量(PV操作)
    作用:互斥和同步.
    原理:锁可以理解为信号量为1. 信号量可以用做资源计数器,允许N个线程同时执行一段代码,但是禁止N+1个线程同时运行.不需要人为干预,操作系统会计数. sem_post每次+1,sem_wait每次-1,当为0的时候不继续做减法,继续保持为0,这个特性很主要.个数{0,M}.c++信号量相当于java的CountDownLatch.
    1)使用信号量实现线程互斥.
    经典问题:哲学家问题,哲学家只有拿到一双筷子才能吃饭,
    (https://img-blog.csdnimg.cn/img_convert/cca5b71d6c94925667eaa078887e822e.png)

    å¾ç¤º
    2) 同步(控制线程时序):一个线程放一个线程取.比如:接收线程接收数据放入queue后sem_post(+1),另一个线程sem_wait(-1)(sem_wait 会阻塞线程)从queue中获取数据.同理, 一个线程业务逻辑处理完将结果放入发送queue,另一个线程发送也一样.
    2.3 条件变量
    作用: 同步(控制线程时序). 线程A发送cond_signal,线程B 在cond_wait 接收(cond_wait 阻塞线程)。cond_wait必须和mutex连用.cond_wait功能是先unlock,然后wait,等到signal后,再lock 。当没有cond_wait的时候,cond_signal 会被ignore.
    网上例子也多的是.
    其中cond_wait 有潜在的问题。核心是取消点,不是运行到哪句都立刻取消的,是到取消点即函数才取消.
    https://www.cnblogs.com/mydomain/archive/2011/08/15/2139830.html
    notes:java条件变量,notify,wait.
    2.4 几种同步方法的异同
    1 ) 锁必须是同一个线程获取以及释放, 否则会死锁.而条件变量和信号量则不必.
    锁不能控制线程先后顺序.
    2 ) 信号的递增与减少会被系统自动记住, 系统内部有一个计数器实现信号量,不必担心会丢失, 而唤醒一个条件变量时,如果没有相应的线程在等待该条件变量, 这次唤醒将被丢失.
    3 ) 锁与信号量转换
    Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个,一般的用法是用于串行化对临界区代码的访问,保证这段代码不会被并行的运行。
    Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。
    对于N=1的情况,称为binary semaphore,一般的用法是,用于限制对于某一资源的同时访问。在有的系统中Binary semaphore与Mutex是没有差异的.
    实现:https://www.cnblogs.com/noble/p/4144015.html
    4) 条件变量与信号量转换
    sem_post 和sem_wait可以替换cond_signal 和cond_wait.
    总之:信号量可以代替锁和条件变量,条件变量和锁联合使用也可以代替信号量.
    扩展: 读写锁:https://blog.csdn.net/hik_zxw/article/details/20064577
    自旋锁:线程处于就绪或者运行状态,不是处于阻塞状态(阻塞需要cpu做系统切换).这样可以随时调度,锁一被释放自己就可以拿到.
    notes:synchronized锁lock类似。前者是乐观锁,后者是悲观锁.
    3 智能指针与多线程#
    指针不同于其他类型变量,指针内存在堆不在栈,需要释放.多线程指针释放容易产生错误,比如:在线程A使用,线程B释放的情况.
    下面是chenshuo老师写的自定义智能指针. 采用无锁华变成,优化拷贝构造函数和赋值构造函数.on
    // A simple reference counted smart pointer.
    // make use of GCC atomic builtins and C++ move semantics
    template<typename T>
    class counted_ptr
    {
      typedef int* counted_ptr::*bool_type;
     public:
      counted_ptr(T* p = nullptr)
        : ptr_(p),
          count_(p ? new int(1) : nullptr) { }
      counted_ptr(const counted_ptr& rhs) noexcept
        : ptr_(rhs.ptr_),
          count_(rhs.count_)
      {
        if (count_)
          __atomic_fetch_add(count_, 1, __ATOMIC_SEQ_CST);//原子操作add
      }
    //右值引用简化深拷贝https://zhuanlan.zhihu.com/p/97128024
      counted_ptr(counted_ptr&& rhs) noexcept
        : ptr_(rhs.ptr_),
          count_(rhs.count_)
      {
        rhs.ptr_ = nullptr;
        rhs.count_ = nullptr;
      }
      ~counted_ptr()
      {
        //退出子函数则调用析构
        reset();
      }
      counted_ptr& operator=(counted_ptr rhs)
      {
        swap(rhs);
        return *this;
      }
      T* get() const noexcept
      {
        return ptr_;
      }
      void reset()
      {
        static_assert(sizeof(T) > 0, "T must be complete type");
        if (count_)
        {
          //原子操作;引用计数为0,则释放内存.如果引用计数不为0,则仅仅将当前的指针设置为null.内存中的东东并不释放.
          if (__atomic_sub_fetch(count_, 1, __ATOMIC_SEQ_CST) == 0)
          {
            delete ptr_; //释放
            delete count_;
          }
          ptr_ = nullptr;
          count_ = nullptr;
        }
      }
      void swap(counted_ptr& rhs) noexcept
      {
        T* tp = ptr_;
        ptr_ = rhs.ptr_;
        /*tp是临时变量,所以退出swap函数的时候rhs.ptr_变成指向Null.提高对rhs中指针的拷贝效率.copy-on-write*/
        rhs.ptr_ = tp;
    
        int* tc = count_;
        count_ = rhs.count_;
        rhs.count_ = tc;
      }
      T* operator->() const noexcept
      {
        // assert(ptr_);
        return ptr_;
      }
      T& operator*() const noexcept
      {
        // assert(ptr_);
        return *ptr_;
      }
      operator bool_type() const noexcept
      {
        return ptr_ ? &counted_ptr::count_ : nullptr;
      }
      int use_count() const noexcept
      {
        return count_ ? __atomic_load_n(count_, __ATOMIC_SEQ_CST) : 0;
      }
      private:
      T* ptr_;
      int* count_;
    };
    

    多线程没有引用计数。而是多线程中的指针有引用计数,计数与线程回收没关系,与内存回收有关。
    4 线程池

    在这里插入图片描述
    一个线程放数据
    g_async_queue_push(handle->queued_packets, pkt);
    //典型的将对象放入线程池,线程池分配一个线程处理对象的queue
    g_thread_pool_push(ice_send_thread_pool, handle, NULL);
    一个线程发送数据
    janus_ice_queued_packet *pkt = g_async_queue_try_pop(handle->queued_packets);
            while (pkt != NULL) {
                int32_t error = janus_send_data(pkt, handle);
                pkt = g_async_queue_try_pop(handle->queued_packets);
            }
    

    下面的link是线程池的实现。android线程池和c++的不太一样,没有调度,resume这些操作,这些都封装好了。而是介绍线程池的几种使用模型. 线程池ThreadPool 全面解析 https://www.jianshu.com/p/0e4a5e70bf0e;
    https://bugstack.blog.csdn.net/article/details/110946298
    5 多线程实例
    1)多线程参数
    a) 传整数.注意是把整数当成地址传递.
    例如: int i=5 ;pthread_create(&m_threadId[i],NULL,m_cbf,(void *)i).
    传递原理: 因为地址可能无效,所以编译的时候会有一条warning.这是一个tricky,把数当成地址传.如(void *) 5表示传地址5.
    pthread_create取值:执行函数是参数3. 执行函数不是取地址5中的值,而且将地址5强制转换成整数. 把地址当成数使用,按整数读取func(__args ) { k=(int)__args;} 即k=5;
    b) 为什么不直接传整数的地址
    int *pi=5; 传pi不就完了.
    why thread 采用这种形式传参数4呢,而不是存数值5所在地址呢?
    因为主线程传的是i的地址,所以每个线程从该地址取值。一定是最后那个5(前面的被冲掉了)

    for (i=0;i<10;i++) {
    p=&i;
    pthread_create(&m_threadId[i],NULL,m_cbf,p);
    }
    

    子线程读取k=(int )q; run的结果是主传0-5,结果全是5.是同一个变量的同一个地址。而(void )i,是传的地址0,1,2,3,4,5是传的不同的地址。
    2) 向线程参数是传对象
    a) c++需要传对象引用,c语言需要传struct地址,否则是传值拷贝.
    b) 线程函数是回调函数,所以必须是static 或者全局函数.
    对于c语言来说,只有上述两种函数. 对于c++ 创建线程时,线程函数如果要设置成类的成员函数,则必须是
    静态
    成员函数,在此函数种不能使用非静态成员变量,如果要使用非静态*成员变量,则一种比较适合线程的方法是:建立线程的时候把this指针传进去.
    5.1 c语言实现

    void *wait(void *t)
    {
       long tid;
       tid = (long)t;
       ...
       pthread_exit(NULL);
    }
    rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
    rc = pthread_join(threads[i], &status);
    

    5.2 c++实现
    c++的线程是全局函数std:thread,直接向thread 传执行函数和参数。中的对象和外面的对象不一样.

    bar1,bar2,bar3为自定义函数.bar1是成员非静态函数,bar2,bar3是成员静态函数.
     thread t1(&foo::bar1, &f, 5); //注意在调用非静态类成员函数时,需要加上实例变量。
     thread t2(&foo::bar2, 4);
     thread t2(&foo::bar3, &node); //传入引用
    

    8 死锁的调试
    死锁现象及分析方法 之一 Linux C https://www.jianshu.com/p/3efe896fbabc
    linux下多线程死锁调试 https://www.jianshu.com/p/2af2a0ab8e08
    参考:https://blog.csdn.net/fdsafwagdagadg6576/article/details/43925029
    ** other:自旋锁**
    自旋锁:单cpu 切换出来,另一个线程就是空转。没意义。---放入lock or 多线程blog
    所以用于多cpu:一个访问临界区,另一个空转等待。
    详见: https://www.jianshu.com/p/4b097aac2c9d
    wait,notify c语言是进程通信。在c++和java变成线程通信
    notify,wait pthread_cond_signal,pthread_cond_wait没发现不同

    如对您有帮助,请随手点个赞,谢谢###

    large.jpg

    相关文章

      网友评论

        本文标题:linux c/c++多线程看了肯定懂

        本文链接:https://www.haomeiwen.com/subject/quibuktx.html