线程传递的参数类型为void*,传递方式有值传递、指针传递、引用传递
值传递:拷贝一份值给新的线程,多线程间不共享
址传递(指针传递):
线程分离后,线程退出自动释放全部资源:pthread_detach()
非分离状态的线程才可以被join回收资源
阻塞回收:pthread_join()
非阻塞回收:pthread_tryjoin_np()
限时阻塞回收:pthread_timedjoin_np()
线程终止的时候,可以调用清理函数释放资源,入栈和出栈函数必须成对的出现
清理函数入栈:pthread_cleanup_push()
清理函数出栈:pthread_cleanup_pop()(0:出栈不执行 非零:出栈并执行)
线程在运行过程中可以调用被取消:pthread_cancel()
线程被取消后,join返回值为PTHREAD_CANCELED 即 -1
设置线程的取消状态:pthread_setcancelstate()
宏:PTHREAD_CANCEL_ASYNCHRONOUS: 立即取消
宏:PTHREAD_CANCEL_DEFERRED: 到达取消点(例如sleep())才取消
设置线程的取消点:pthread_testcancel()
多个线程访问共享资源(全局和静态变量)的时候会冲突
例:定义全局变量int a=0; 线程1和线程2同时:循环执行 a++ 一万次 得到a的值小于2万,a并不会自动到两万
三个概念:原子性、可见性、顺序性
可见性:
当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。
CPU有高速缓存。每个线程读取共享变量时,会将该变量从内存加载到CPU的缓存中,修改该变量后,CPU会立即更新缓存,但不一定会立即将它写回内存。此时其它线程访问该变量,从内存中读到的是旧数据,而非第一个线程更新后的数据。
顺序性:
程序执行的顺序按照代码的先后顺序执行。
CPU为了提高程序整体的执行效率,可能会对代码进行优化,按照更高效的顺序执行代码。
CPU虽然并不保证完全按照代码顺序执行,但它会保证程序最终的执行结果和代码顺序执行时的结果一致。
例:int a = 1; a = 2; a = 3; 编译器对代码优化为int a = 3;
volatile关键字:保证可见性和禁止代码优化,但不是原子操作
解决线程安全问题:原子操作(c++原子类)、和线程同步(锁)
原子操作:本质是总线锁
等待锁的时候,线程会休眠,不会消耗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(); //销毁锁
循环的加测锁是否可用,会消耗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(); //销毁锁
读时共享,写时单独,适用于读的次数远大于写的场景
定义锁:
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时间片还未使用完,获取到锁的概率会很大
主线程创建socket连接,一个子线程负责发送,另一个子线程负责接收