Linux多线程基础总结

一、线程参数传递

线程传递的参数类型为void*,传递方式有值传递、指针传递、引用传递

  1. 值传递:拷贝一份值给新的线程,多线程间不共享

  2. 址传递(指针传递):

    • 连续创建多个线程中如果传入的是同一变量地址,多线程共享这变量,由于哪个线程先运行是不确定的,变量的值是不可控的,并且会造成共享资源竞争问题,因为修改变量的值不是原子操作
    • 如果主线程先结束,其内存空间会被释放,子进程中的指针就成了野指针,可想而知,继续操作这块内存会造成意想不到的后果。
      结果方案:定义的变量使用堆内存分配,可以避免栈内存释放,也可以使用全局变量

二、线程分离

线程分离后,线程退出自动释放全部资源:pthread_detach()

三、线程资源的回收

非分离状态的线程才可以被join回收资源

  1. 阻塞回收:pthread_join()

  2. 非阻塞回收:pthread_tryjoin_np()

  3. 限时阻塞回收:pthread_timedjoin_np()

四、线程清理函数

线程终止的时候,可以调用清理函数释放资源,入栈和出栈函数必须成对的出现

  1. 清理函数入栈:pthread_cleanup_push()

  2. 清理函数出栈:pthread_cleanup_pop()(0:出栈不执行 非零:出栈并执行)

五、线程取消

  1. 线程在运行过程中可以调用被取消:pthread_cancel()

  2. 线程被取消后,join返回值为PTHREAD_CANCELED 即 -1

  3. 设置线程的取消状态:pthread_setcancelstate()
    宏:PTHREAD_CANCEL_ASYNCHRONOUS: 立即取消
    宏:PTHREAD_CANCEL_DEFERRED: 到达取消点(例如sleep())才取消
    设置线程的取消点:pthread_testcancel()

六、线程与信号

  1. 向指定线程发送信号:pthread_kill()
  2. 信号屏蔽:进程:sigpromask() 线程:pthread_sigmask()
  3. 阻塞等待信号:sigwait()sigwaitinfo()sigtimedwait()
  4. 在多线程中,外部向进程发送信号(ctl+c)不会中断系统调用(sleep()
  5. 在多线程中,信号的处理是所有线程共享的(即信号注册在任一线程中注册都可以)
  6. 进程中的信号可以送达单个线程,会中断系统调用
  7. 如果某个线程因为信号(SIGTERM默认处理)而终止,整个进程将终止

七、线程安全

  1. 多个线程访问共享资源(全局和静态变量)的时候会冲突
    例:定义全局变量int a=0; 线程1和线程2同时:循环执行 a++ 一万次 得到a的值小于2万,a并不会自动到两万

  2. 三个概念:原子性、可见性、顺序性

    • 原子性
      一个操作(有可能包含有多个子操作)要么全部执行(生效)要么全部都不执行(都不生效)
      CPU执行指令:读取指令、读取内存、执行指令、写回内存
      例(非原子性):第一不读取指令:i++ 第二步:从内存中读取i的值 第三步:把i+1 第四步:把结果写回内存
    • 可见性
      当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。
      CPU有高速缓存。每个线程读取共享变量时,会将该变量从内存加载到CPU的缓存中,修改该变量后,CPU会立即更新缓存,但不一定会立即将它写回内存。此时其它线程访问该变量,从内存中读到的是旧数据,而非第一个线程更新后的数据。

    • 顺序性
      程序执行的顺序按照代码的先后顺序执行。
      CPU为了提高程序整体的执行效率,可能会对代码进行优化,按照更高效的顺序执行代码。
      CPU虽然并不保证完全按照代码顺序执行,但它会保证程序最终的执行结果和代码顺序执行时的结果一致。
      例:int a = 1; a = 2; a = 3; 编译器对代码优化为int a = 3;

  3. volatile关键字:保证可见性和禁止代码优化,但不是原子操作

  4. 解决线程安全问题:原子操作(c++原子类)、和线程同步(锁)
    原子操作:本质是总线锁

八、线程同步

1. 互斥锁

等待锁的时候,线程会休眠,不会消耗CPU,适合等待时间可能很长的场景

声明锁

pthread_mutex_t mutex;
  • 普通锁: PTHREAD_MUTEX_TIMED_NP
    当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。
    这种锁策略保证了资源分配的公平性。

  • 嵌套锁PTHREAD_MUTEX_RECURSIVE_NP
    允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。
    如果是不同线程请求,则在加锁线程解锁时重新竞争。

  • 适应锁PTHREAD_MUTEX_ADAPTIVE_NP
    解锁后,请求锁的线程重新竞争。

	int pthread_mutex_init();     //初始化锁,也可定义时直接使用宏初始化如上
	int pthread_mutex_lock();     //等待并加锁
	int pthread_mutex_trylock();  //尝试加锁,不等待
	int pthread_mutex_timedlock();//带超时机制的加锁
	int pthread_mutex_unlock()    //解锁
	int pthread_mutex_destroy();  //销毁锁
2. 自旋锁

循环的加测锁是否可用,会消耗CPU,适合等待时间很短的场景

pthread_spinlock_t mutex;   //声明锁
int pthread_spin_init();    //初始化锁
int pthread_spin_lock();    //等待并加锁
int pthread_spin_trylock(); //尝试加锁,不等待
int pthread_spin_unlock();  //解锁
int pthread_spin_destroy(); //销毁锁
4. 读写锁

读时共享,写时单独,适用于读的次数远大于写的场景

  • 定义锁

     pthread_rwlock_t mutex; 		//声明锁
     PTHREAD_RWLOCK_INITIALIZER		//使用宏初始化锁
     int pthread_rwlock_init();		//初始化锁
     int pthread_rwlock_destroy();	//销毁锁
    
  • 锁属性

    int pthread_rwlockattr_getpshared();//获取读写锁属性
    int pthread_rwlockattr_setpshared();//设置读写锁属性
    PTHREAD_PROXESS_PRIVATE(单个线程私有)
    PTHREAD_PROCESS_SHARED(多线程共享)
    
  • 读锁

    int pthread_rwlock_rdlock();	  //阻塞获取读锁
    int pthread_rwlock_tryrdlock();   //尝试获取读锁,不阻塞
    int pthread_rwlock_timedrdlock(); //获取读锁,带超时机制
    
  • 写锁

    int pthread_rwlock_wrlock();	  //阻塞获取写锁
    int pthread_rwlock_trywrlock();	  //尝试获取写锁,不阻塞
    int pthread_rwlock_timedwrlock(); //获取写锁,带超时机制
    

注意:只有在不加锁时,才能获取到写锁。linux系统优先考虑获取读锁,获取写锁的线程需要等待所有读锁释放才能获得到锁

九、条件变量

pthread_cond_t cond; 	     //声明条件变量
PTHREAD_COND_INITIALIZER;    //使用宏初始化条件变量
int pthread_cond_init();     //初始化条件变量
int pthread_cond_destroy();  //销毁条件变量

int pthread_cond_wait();     //等待被唤醒进行加锁
int pthread_cond_timedwait();//等待被唤醒进行加锁,带超时机制

int pthread_cond_signal();   //唤醒至少一个等待中的线程
int pthread_cond_broadcast();//唤醒全部等待中的线程

int pthread_condattr_getpshared();//获取共享属性
int pthread_condattr_setpshared();//设置共享属性(单个线程私有、多线程共享)

十、信号量

多进程的信号量可以用在多线程中,而多线程的信号量只能用于多线程中,多线程的信号量使用比较简单

sem_t *sem;  	  //声明信号量
int sem_init();   //初始化信号量
int sem_destroy();//销毁信号量

int sem_wait(sem_t *sem);   //信号量的P操作
int sem_trywait(sem_t *sem);//信号量的P操作,不阻塞
int sem_timedwait();	    //信号量的P操作,带超时机制
int sem_post(sem_t *sem);   //信号量的V操作
int sem_getvalue();	    //获取信号量的值

注意:以上几种同步机制都能形成等待队列,但是不是绝对公平的,当前线程的cpu时间片还未使用完,获取到锁的概率会很大

十一、生产者消费者模型

  1. 互斥锁+条件变量实现
  2. 信号量实现

十二、保证多线程程序的稳定性

  1. 主进程只用与监控和调度
  2. 程序的功能由子线程实现,把心跳写入全局变量
  3. 如果心跳超时,取消子线程再重新启动

十三、多线程实现异步通讯

主线程创建socket连接,一个子线程负责发送,另一个子线程负责接收

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值