一,线程函数
1、pthread_attr_t 线程参数结构体
使用时需要调用pthread_attr_init(&attr)初始化结构体;pthread_attr_destroy(&attr)释放结构体.
1.1、pthread_attr_setdetachstate
比如pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED),可以在创建线程时就可以设定为内核接管
1.2、pthread_attr_setschedpolicy设置调度策略
SCHED_OTHER:默认,分时策略,优先级永远比实时低,同是分时策略其优先级是动态计算的,好久没有调用的线程具有较高优先级
SCHED_FIFO:实时策略,优先级范围1-99,高优先级可以抢占低优先级,同优先级FIFO,即直至主动释放CPU
SCHED_RR:SCHED_FIFO+同优先级用时间片,比SCHED_FIFO多了时间片,同优先级循环调用
可以从字面意思理解分时与实时,分时保证任务可以完成,实时保证实时,其实高优先级本身就是实时,一个进程进程不能全是实时,分时策略决定优先级考虑了过去,但实时没有
2、int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
使用该函数需要包含头文件<pthread.h>。
thread:其实是long指针,保存创建的线程的线程 ID
pthread_attr_t:定义了线程的各种属性,如果将参数 attr 设置为 NULL,那么表示将线程的所有属性设置为默认值,以此创建新线程。
start_routine:新创建的线程从 start_routine()函数开始运行,返回值类型为void *,并且该函数的参数是void *,这个参数其实就是 arg。
arg:一般情况下,需要将 arg 指向一个全局或堆变量,意思就是说在线程的生命周期中,该 arg 指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。当然也可将参数 arg 设置为 NULL,表示不需要传入参数给 start_routine()函数。
3、void pthread_exit(void *retval)
如果进程中的任意线程调用 exit()、_exit()或者_Exit(),那么将会导致整个进程终止
4、int pthread_join(pthread_t thread, void **retval)
会以阻塞的形式等待指定的线程终止,在父、子进程当中,父进程可通过 wait()函数(或其变体 waitpid())阻塞等待子进程退出并获取其终止状态
retval:如果参数 retval 不为 NULL,则 pthread_join()将目标线程的退出状态pthread_exit返回的值
5、int pthread_cancel(pthread_t thread)
与pthread_exit不同点这个是终止非当前线程
6、int pthread_detach(pthread_t thread)
默认情况下,当线程终止时,其它线程可以通过调用 pthread_join()获取其返回状态、回收线程资源,有时,程序员并不关系线程的返回状态,只是希望系统在线程终止时能够自动回收线程资源并将其移除。在这种情况下,可以调用 pthread_detach()将指定线程进行分离
二,线程安全函数
如图,再一个不可重入函数添加锁,那他就变成一个线程安全函数,但它还是不可重入
1.1、可重入函数可以分为两类:
⚫ 绝对的可重入函数:所谓绝对,指的是该函数不管如何调用,都刚断言它是可重入的,都能得到预期的结果。
⚫ 带条件的可重入函数:指的是在满足某个/某些条件的情况下,可以断言该函数是可重入的,不管怎么调用都能得到预期的结果
1.2、绝对可重入函数的特点:
⚫ 函数内所使用到的变量均为局部变量,换句话说,该函数内的操作的内存地址均为本地栈地址;
⚫ 函数参数和返回值均是值类型;
⚫ 函数内调用的其它函数也均是绝对可重入函数。
2、man 7 pthreads 可以查阅所有线程不去安全函数
三,互斥锁(mutex)
互斥锁使用结构体 pthread_mutex_t 数据类型表示,在使用互斥锁之前,必须首先对它进行初始化操作,可以使用两种方式对互斥锁进行初始化操作。
1、互斥锁初始化
方式由两种,当pthread_mutex_init的pthread_mutexattr_t为空的时候和第一种效果一样
方式1:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 方式2:pthread_mutexattr_t mutexattr; pthread_mutexattr_init(&mutexattr); pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE_NP);// 嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。 pthread_mutex_init(&SIGNAL_GROUP_DATA, &mutexattr); //销毁:pthread_mutex_destroy() pthread_mutexattr_destroy(&mutexattr);
1.1、互斥锁属性设置:pthread_mutexattr_settype
pthread_mutexattr_settype可以设置类型,互斥锁的类型属性控制着互斥锁的锁定特性,常用4种:
①PTHREAD_MUTEX_NORMAL:标准的互斥锁类型,不做任何的错误检查或死锁检测。如果①线程试图对已经由自己锁定的互斥锁再次进行加锁,则发生死锁;②互斥锁处于未锁定状态,或者已由其它线程锁定,对其解锁会导致不确定结果。
②PTHREAD_MUTEX_ERRORCHECK:此类互斥锁会提供错误检查。譬如这三种情况都会导致返回错误:①线程试图对已经由自己锁定的互斥锁再次进行加锁(同一线程对同一互斥锁加锁两次),返回错误;②线程对由其它线程锁定的互斥锁进行解锁,返回错误;③线程对处于未锁定状态的互斥锁进行解锁,返回错误。这类互斥锁运行起来比较慢,因为它需要做错误检查,不过可将其作为调试
③PTHREAD_MUTEX_RECURSIVE:此类互斥锁允许同一线程在互斥锁解锁之前对该互斥锁进行多次加锁,然后维护互斥锁加锁的次数,把这种互斥锁称为递归互斥锁,但是如果解锁次数不等于加速次数,则是不会释放锁的;
④PTHREAD_MUTEX_DEFAULT : 此 类 互 斥 锁 提 供 默 认 的 行 为 和 特 性 。 使 用 宏PTHREAD_MUTEX_INITIALIZER 初 始 化 的 互 斥 锁 , 或 者 调 用 参 数 arg 为 NULL 的pthread_mutexattr_init()函数所创建的互斥锁,都属于此类型。此类锁意在为互斥锁的实现保留最大灵活性,在 Linux 上 ,PTHREAD_MUTEX_DEFAULT 类 型 互 斥 锁 的 行 为 与PTHREAD_MUTEX_NORMAL 类型相仿。
2、互斥锁加锁
阻塞式:pthread_mutex_lock()/pthread_mutex_unlock()
非阻塞:pthread_mutex_trylock()
四,条件变量(cond)
条件变量是线程可用的另一种同步机制。条件变量用于自动阻塞线程,知道某个特定事件发生或某个条件满足为止,通常情况下,条件变量是和互斥锁一起搭配使用的。
1、条件变量初始化
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); pthread_cond_t cond=PTHREAD_COND_INITIALIZER; int pthread_cond_destroy(pthread_cond_t *cond);
2、通知和等待条件变量
int pthread_cond_broadcast(pthread_cond_t *cond);//这个会保证唤醒所有线程,但一个线程获取锁后其它线程会休眠 int pthread_cond_signal(pthread_cond_t *cond);//保证唤醒至少一个线程,如果只有一个线程推荐这种方式 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); //线程休眠时:unlock mutex ;线程收到信号唤醒时:lock mutex
3、使用实例
//消费者线程 for ( ; ; ) { pthread_mutex_lock(&mutex);//上锁 while (0 >= g_avail) pthread_cond_wait(&cond, &mutex);//解锁mutex等待条件满足;条件满足后上锁返回 while (0 < g_avail) g_avail--; //消费 pthread_mutex_unlock(&mutex);//解锁 } //生产者线程 pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); for ( ; ; ) { pthread_mutex_lock(&mutex);//上锁 g_avail++; //生产 pthread_mutex_unlock(&mutex);//解锁 pthread_cond_signal(&cond);//向条件变量发送信号 }
五,自旋锁(spin)
互斥锁是基于自旋锁来实现的,所以自旋锁相较于互斥锁更加底层,“自旋”其实就是调用者一直在循环查看该自旋锁的持有者是否已经释放了锁,“自旋”一词因此得名.自旋锁的不足之处在于:自旋锁一直占用的 CPU,它在未获得锁的情况下,一直处于运行状态(自旋),所以占着 CPU,如果不能在很短的时间内获取锁,这无疑会使 CPU 效率降低。
综上所述,再来总结下自旋锁与互斥锁之间的区别:
⚫ 互斥锁是基于自旋锁而实现的,所以自旋锁相较于互斥锁更加底层;
⚫ 开销上的区别:获取不到互斥锁会陷入阻塞状态(休眠),直到获取到锁时被唤醒;而获取不到自旋锁会在原地“自旋”,直到获取到锁;休眠与唤醒开销是很大的,所以互斥锁的开销要远高于自旋锁、自旋锁的效率远高于互斥锁;但如果长时间的“自旋”等待,会使得 CPU 使用效率降低,故自旋锁不适用于等待时间比较长的情况。
⚫ 使用场景的区别:自旋锁在用户态应用程序中使用的比较少,通常在内核代码中使用比较多;因为自旋锁可以在中断服务函数中使用,而互斥锁则不行,在执行中断服务函数时要求不能休眠、不能被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了CPU 使用权,休眠结束时无法返回到中断服务函数中,这样就会导致死锁!
六, 读写锁(rwlock)
读写锁也叫做共享互斥锁。当读写锁是读模式锁住时,就可以说成是共享模式锁住。当它是写模式锁住时,就可以说成是互斥模式锁住。
读写锁有如下两个规则:
①写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读模式加锁还是以写模式加锁)的线程都会被阻塞。
②读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止。
读写锁非常适合于对共享数据读的次数远大于写的次数的情况。
1、读写锁初始化
方式由两种,当pthread_rwlock_init的pthread_rwlockattr_t为空的时候和第一种效果一样
方式1:pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; 方式2:int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
2、读写锁加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//阻塞模式 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//非阻塞 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//阻塞模式 int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//非阻塞 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);