美文网首页程序员
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

相关文章

  • Android 多线程

    目录 AsyncTask使用和源码分析: 线程同步-锁: linux c/c++多线程看了肯定懂: 正文 一 多线...

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

    1 线程编译 gcc -o pthread -lpthread pthread.c 2 线程和进程的区别 进程线程...

  • c++操作系统类编程 - read list

    Concurrency C++ in Action Linux多线程服务端编程:使用muduo C++网络库 现代...

  • 多线程编程

    参考:C++ 并发编程 线程 windsows多线程 new thread(...) linux 多线程: pth...

  • 每天阅读两小时

    前面四个月把“深度探索C++对象模型”看了下(算是复习,之前看过几次),把“Linux服务端多线程编程”看了大部分...

  • 技术学习方法论

    C++: 如何看懂《Linux多线程服务端编程——使用muduoC++网络库》于洋的回答

  • C++ 多线程

    C++ 多线程 | 菜鸟教程 C++ 11 多线程--线程管理 - Brook_icv - 博客园

  • Linux 中的线程局部存储(1)

    在Linux系统中使用C/C++进行多线程编程时,我们遇到最多的就是对同一变量的多线程读写问题,大多情况下遇到这类...

  • linux # .a & .so

    Linux Library Types: There are two Linux C/C++ library ty...

  • C++主线程调用Java方法

    1. C++ 全局调用Java方法 1.1 C++主线程调用Java方法 在 Android C++多线程-创建子...

网友评论

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

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