linux多线程使用和注意

目录
1 线程编译
2 线程和进程的区别
3 有关线程操作的函数
4 线程互斥和同步(也叫线程安全)
4.1 互斥
4.2 信号量
4.3 条件变量
5 智能指针与多线程
6 线程池
7 多线程的实现
7.1 c语言实现
7.2 c++实现
7.3 java 实现
8 死锁的调试
正文
1 线程编译 gcc -o pthread -lpthread pthread.c
2 线程和进程的区别

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

3 有关线程操作的函数
线程的创建 pthread_create;线程的接收,pthread_join 主线程阻塞接收子线程.pthread_detach子线程脱离主线程.pthread_exit (void *status) 子线程退出;pthread _cancel() A线程结束B线程.
4 线程同步
大部分项目都会用到线程同步,而且下面的3种方法都会用到.
为什么要线程同步?

  1. 防止多个线程共同访问一个全局变量或者函数产生错误. 多线程访问多一个函数的局部变量会产生错误吗?答案是不会,因为每个线程有自己的栈,局部变量保存在自己的栈中.
  2. 线程时序控制
    4.1 互斥:使用方法:锁.作用:防止多个线程访问互相影响,作用与下面2个不同.
pthread_mutex_lock(&rd);
readCount++;
pthread_mutex_unlock(&rd)

 

  linux多线程使用和注意 image.gif
具体实例网上很多.
4.2 信号量
作用:互斥和同步.
原理:锁可以理解为信号量为1. 信号量是锁的扩展,允许N个线程同时执行一段代码,但是禁止N+1个线程同时运行.相当于java的CountDownLatch.
1)经典问题:哲学家问题,哲学家只有拿到一双筷子才能吃饭,使用信号量实现线程互斥.
  linux多线程使用和注意 å¾ç¤º
  linux多线程使用和注意 image.gif ​
  linux多线程使用和注意 å¾ç¤º
  linux多线程使用和注意 image.gif ​
2) 同步(控制线程时序):一个线程放一个线程取.比如:接收线程接收数据放入queue后sem_post(+1),另一个线程sem_wait(-1)(sem_wait 会阻塞线程)从queue中获取数据.同理, 一个线程业务逻辑处理完将结果放入发送queue,另一个线程发送也一样.
4.3 条件变量
作用: 同步(控制线程时序). 线程A发送cond_signal,线程B 在cond_wait 接收(cond_wait 阻塞线程)。
网上例子也多的是.
其中cond_wait 有潜在的问题。核心是取消点,不是运行到哪句都立刻取消的,是到取消点即函数才取消.
https://www.cnblogs.com/mydomain/archive/2011/08/15/2139830.html
5 智能指针与多线程
指针不同于其他类型变量,指针内存在堆不在栈,需要释放.多线程指针释放容易产生错误,比如:在线程A使用,线程B释放的情况.
下面是chenshuo老师写的自定义智能指针. 采用无锁华变成,优化拷贝构造函数和赋值构造函数.

 

// 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_;
};

 

  linux多线程使用和注意 image.gif
6 线程池
一个线程放数据
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);
}
7 多线程的实现
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指针传进去.
7.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);
  linux多线程使用和注意 image.gif

7.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); //传入引用

 

  linux多线程使用和注意 image.gif
7.3 java 实现
参见:JAVA多线程之间实现同步+多线程并发同步解决方案 https://blog.csdn.net/yz2015/article/details/79436123
  linux多线程使用和注意 image
  linux多线程使用和注意 image.gif

作者:mfdalf
链接:https://www.jianshu.com/p/00a97bac4913
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
上一篇:2019CCPC网络赛 HDU6705 - path K短路


下一篇:python高级教程