基本线程编程
pthread_join 当多个线程同时等待时,会有随机一个线程返回成功0,其他线程将失败并返回ESRCH错误 并且pthread_join仅适用于非线程分离时
线程有单独的线程数据,可以使用pthread_key_create创建,pthread_setspecific设置数据绑定,pthread_getspecial获取数据
使用一个全局变量,每个线程可以set/get,并且key的个数可以通过PTHREAD_KEY_MAX(定义于limits.h文件中)或者系统调用sysconf(_SC_THREAD_KEYS_MAX)来调整 pthread_once代表线程只初始化一次 sched_yield可以使当前线程停止运行,以便执行更一个更高优先级的线程 pthread_setschedparam函数用来设置线程的优先级,当线程默认是SCHED_OTHER分时调度策略时,优先级设置不起作用,在SCHED_FIFO先入先出模式下起作用
pthread_sigmask 用于屏蔽线程中的信号 ■ SIG_BLOCK。向当前的信号掩码中添加 new,其中 new 表示要阻塞的信号组。 ■ SIG_UNBLOCK。从当前的信号掩码中删除 new,其中 new 表示要取消阻塞的信号组。 ■ SIG_SETMASK。将当前的信号掩码替换为 new,其中 new 表示新的信号掩码。
pthread_exit(status); 如果主线程仅仅调用了 pthread_exit,则仅主线程本身终止。进程及进程内的其 他线程将继续存在。所有线程都已终止时,进程也将终止。
如果不调用pthread_join和pthread_detail,线程堆栈和线程描述符(总计8K多)将会泄露
线程属性
pthread_attr_init设置线程属性 线程的默认属性如下 scope PTHREAD_SCOPE_PROCESS 新线程与进程中的其他线程发生竞 争。 detachstate PTHREAD_CREATE_JOINABLE 线程退出后,保留完成状态和线程 ID。 stackaddr NULL 新线程具有系统分配的栈地址。 stacksize 0 新线程具有系统定义的栈大小。 priority 0 新线程的优先级为 0。 inheritsched PTHREAD_EXPLICIT_SCHED 新线程不继承父线程调度优先级。 schedpolicy SCHED_OTHER 新线程对同步对象争用使用 Solaris 定义的固定优先级。线程将一直运行,直到被抢占
线程的调度策略 SCHED_OTHER 分时调度策略,(默认的) SCHED_FIFO实时调度策略,先到先服务 SCHED_RR实时调度策略,时间片轮转 other模式可以通过设置nice值指定哪个线程先运行 fifo只支持priority优先级 RR是 nice值和priotiry的结合
pthread_attr_setdetachstate 设置线程的分离状态,可以重用线程ID和其他资源 /* initialized with default attributes */ ret = pthread_attr_init (&tattr); ret = pthread_attr_setdetachstate (&tattr,PTHREAD_CREATE_DETACHED); ret = pthread_create (&tid, &tattr, start_routine, arg)
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize); 为栈提供溢出保护区 将 guardsize 的值向上舍入为可配置的系统变量 PAGESIZE 的倍数。请 参见 sys/mman.h 中的 PAGESIZE
int pthread_attr_setscope(pthread_attr_t *tattr,int scope); 使用 PTHREAD_SCOPE_SYSTEM 时,此线程将与系统中的所有线程 进行竞争。使用 PTHREAD_SCOPE_PROCESS 时,此线程将与进程中的其他线程进行竞争。
int pthread_setconcurrency(int new_level) 此接口可不用过分关注 通知系统其所需的并发级别。对于 Solaris 9 发行版中引入的线程 实现,此接口没有任何作用,所有可运行的线程都将被连接到 LWP
int pthread_attr_setschedpolicy(pthread_attr_t *tattr, int policy); 设置调度策略接口
int pthread_attr_setinheritsched(pthread_attr_t *tattr, int inherit); inherit 值 PTHREAD_INHERIT_SCHED 表示新建的线程将继承创建者线程中定义的调度策略。将 忽略在 pthread_create() 调用中定义的所有调度属性。如果使用缺省值 PTHREAD_EXPLICIT_SCHED,则将使用 pthread_create() 调用中的属性。
一般情况下,不需要为线程分配栈空间。系统会为每个线程的栈分配 1 MB(对于 32 位系 统)或 2 MB(对于 64 位系统)的虚拟内存
int pthread_attr_setstack(pthread_attr_t *tattr,void *stackaddr, size_t stacksize); 可以设置栈的地址,自己malloc一块内存,将地址作为入参传入stackaddr
用同步对象编程
如果针对以前初始化的但尚未销毁的互斥锁调用pthread_mutex_init(),则该互 斥锁不会重新初始化。
int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared); 互斥锁变量可以是进程专用的(进程内)变量,也可以是系统范围内的(进程间)变量。 要在多个进程中的线程之间共享互斥锁,可以在共享内存中创建互斥锁,并将 pshared 属性 设置为 PTHREAD_PROCESS_SHARED。 此行为与最初的 Solaris 线程实现中 mutex_init() 中的 USYNC_PROCESS 标志等效。 如果互斥锁的 pshared 属性设置为 PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建 的线程才能够处理该互斥锁。
int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type); 设置互斥锁的类型 PTHREAD_MUTEX_NORMAL 不会主动检测死锁,AB BA的情况及容易发生死锁 PTHREAD_MUTEX_ERRORCHECK 可以提供错误检查,当加锁未加锁时或解锁已解锁时,会报错 PTHREAD_MUTEX_RECURSIVE 单线程可以重复对一个锁进行加锁,加锁几次就要解锁几次,然后其他线程才可以使用 PTHREAD_MUTEX_DEFAULT Solaris 线程时,会映射到PTHREAD_MUTEX_NORMAL
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol); protocol 可定义应用于互斥锁属性对象的协议。 PTHREAD_PRIO_NONE 线程的优先级和调度不会受到互斥锁拥有权的影响 PTHREAD_PRIO_INHERIT 会影响优先级的调度,如果th2处于阻塞状态,无论其他线程的优先级如果,th1(INHERIT)都会以高优先级的方式运行 PTHREAD_PRIO_PROTECT th1 (PROTECT)初始化后,th2会以其线程优先级或拥有互斥锁的最高优先级上限运行,一个线程可以同时拥有多个混合使用 PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行
int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, int prioceiling, int *oldceiling); 要避免优先级倒 置,请将 prioceiling 设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。
int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr, int *robustness); 仅当定义了符号_POSIX_THREAD_PRIO_INHERIT 时,pthread_mutexattr_setrobust_np() 才适用。 robustness 定义在互斥锁的属主失败时的行为。pthread.h 中定义的 robustness 的值为 PTHREAD_MUTEX_ROBUST_NP 或 PTHREAD_MUTEX_STALLED_NP。缺省值为 PTHREAD_MUTEX_STALLED_NP。
■ PTHREAD_MUTEX_ROBUST_NP 如果互斥锁的属主失败,则以后对 pthread_mutex_lock() 的所有调用将以不确定的方式 被阻塞。 ■ PTHREAD_MUTEX_STALLED_NP 互斥锁的属主失败时,将会解除锁定该互斥锁。互斥锁的下一个属主将获取该互斥锁, 并返回错误 EOWNWERDEAD。
避免死锁的方式是锁分成,多个线程在锁定多个互斥锁时,以同样的顺序进行锁定,如果不能保证按顺序,则需要使用trylock,使用trylock来避免死锁 pthread_mutex_lock(&m1); pthread_mutex_lock(&m2); /* no processing */ pthread_mutex_unlock(&m2); pthread_mutex_unlock(&m1);
for (; 😉 { pthread_mutex_lock(&m2); if(pthread_mutex_trylock(&m1)==0) /* got it / break; / didn’t get it / pthread_mutex_unlock(&m2); } / get locks; no processing */ pthread_mutex_unlock(&m1); pthread_mutex_unlock(&m2);
当多个线程同时删除单链表中的数据时,可参照以下单链表定义 typedef struct node1 { int value; struct node1 *link; pthread_mutex_t lock; } node1_t;
因为涉及到两个节点的修改,所以每次要锁定两个节点,当前节点以及前序节点
int pthread_condattr_init(pthread_condattr_t *cattr); 调用此函数时,pshared 属性的缺省值为 PTHREAD_PROCESS_PRIVATE。pshared 的该值表示可以 在进程内使用已初始化的条件变量。
int pthread_condattr_setpshared(pthread_condattr_t *cattr, int pshared); pthread_condattr_setpshared(3C) 可用来将条件变量的范围设置为进程专用(进程内)或 系统范围内(进程间)。
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex); 该条件获得信号之前,该函数一直被阻塞。该函数会在被阻塞之前以原子方式释放相关的 互斥锁,并在返回之前以原子方式再次获取该互斥锁。
同样pthread_cond_wait要与while循环相匹配,因为pthread_cond_signal会导致所有等待该条件的线程解除阻塞并尝试再次获取互斥锁,必须要重新测试导致等待的条件
pthread_cond_timedwait() 函数会一直阻塞,直到该条件获得信号,或者最后一个参数所指 定的时间已过为止。
pthread_cond_reltimedwait_np(3C) 的用法与 pthread_cond_timedwait() 的用法基本相同, 唯一的区别在于 pthread_cond_reltimedwait_np() 会采用相对时间间隔而不是将来的绝对时 间作为其最后一个参数的值
以下是生产者V,消费者P具体实例
pthread_mutex_t lock;
phtread_cond_t cond_less;
pthread_cond_t cond_more;
void producer(缓冲区)
{
pthread_mutex_lock(lock);
while(缓冲区已满){
pthread_cond_wait(cond_less);
}
写入缓存区
pthread_cond_signal(cond_more)
pthread_mutex_unlock(lock)
}
void consumer(缓冲区)
{
pthread_mutex_lock(lock);
while(缓冲区已空){
pthread_cond_wait(cond_more);
}
写入缓存区
pthread_cond_signal(cond_less)
pthread_mutex_unlock(lock)
}
为什么要与pthread_mutex 一起使用呢?
这是为了应对 线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候,此时线程2调用了 cond_singal 的情况。 如果不用mutex锁的话,这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cond_wait() 释放锁并进入wait_cond状态 ,此时线程2上锁) 的时候才能调用cond_singal.
int sem_init(sem_t *sem, int pshared, unsigned int value) 信号量初始化,如果 pshared 的值为零,则不能在进程之间共享信号。如果 pshared 的值不为零,则可以在 进程之间共享信号。
void productor(void *arg)
{
int i,nwrite;
while(time(NULL) < end_time){
sem_wait(&avail);
sem_wait(&mutex);
if((nwrite=write(fd,"hello",5))==-1)
{
if(errno==EAGAIN)
printf("The FIFO has not been read yet.Please try later\n");
}
else
printf("write hello to the FIFO\n");
sem_post(&full);
sem_post(&mutex);
sleep(1);
}
}
void consumer(void *arg)
{
int nolock=0;
int ret,nread;
while(time(NULL) < end_time){
sem_wait(&full);
sem_wait(&mutex);
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1){
if(errno==EAGAIN)
printf("no data yet\n");}
printf("read %s from FIFO\n",buf_r);
sem_post(&avail);
sem_post(&mutex);
sleep(1);
}
}
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared); PTHREAD_PROCESS_SHARED 描述:允许可访问用于分配读写锁的内存的任何线程对读写锁进行处理。 即使该锁是在由 多个进程共享的内存中分配的,也允许对其进行处理。 PTHREAD_PROCESS_PRIVATE 描述:读写锁只能由某些线程处理,这些线程与初始化该锁的线程在同一进程中创建。如 果不同进程的线程尝试对此类读写锁进行处理,则其行为是不确定的。由进程共享的属 性的缺省值为 PTHREAD_PROCESS_PRIVATE。
在读写锁机制下,允许同时有多个读者读访问共享资源,只有写者才需要独占资源 写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。 读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。
同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。 读写锁出于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。 读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁请求会被阻塞,以避免写者长时间的不写锁。
线程信号
#include<stdio.h>
#include<pthread.h>
#include<signal.h>
static void sig_alrm(int signo);
static void sig_init(int signo);
int
main()
{
sigset_t set;
int sig;
sigemptyset(&set);
sigaddset(&set, SIGALRM);
pthread_sigmask(SIG_SETMASK, &set, NULL);
signal(SIGALRM, sig_alrm);
signal(SIGINT, sig_init);
sigwait(&set, &sig);
switch(sig){
case 14:
printf("sigwait, receive signal SIGALRM\n");
break;
default:
break;
}
sigdelset(&set, SIGALRM);
pthread_sigmask(SIG_SETMASK, &set, NULL);
for(;;)
{}
return 0;
}
static void
sig_alrm(int signo)
{
printf("after sigwait, catch SIGALRM\n");
fflush(stdout);
return ;
}
static void
sig_init(int signo)
{
printf("catch SIGINT\n");
return ;
}
编程原则
有三种死锁方式 1 单线程重复加锁 2 AB BA类型的加锁 3 线程释放锁后自己又抢占到锁,使其他线程无法得到锁,使用pthread_yield 去调度 请遵循以下的简单锁定原则。
■ 请勿尝试在可能会对性能造成不良影响的长时间操作(如 I/O)中持有锁。 ■ 请勿在调用模块外且可能重进入模块的函数时持有锁。 ■ 一般情况下,请先使用粗粒度锁定方法,确定瓶颈,并在必要时添加细粒度锁定来缓解 瓶颈。大多数锁定都是短期持有,而且很少出现争用。因此,请仅修复测得争用的那些 锁定。 ■ 使用多个锁定时,通过确保所有线程都按相同的顺序获取锁定来避免死锁。
|