Linux多线程编程手册总结

基本线程编程

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);       //将信号集set清空
        sigaddset(&set, SIGALRM);   //添加阻塞信号SIGALRM
        pthread_sigmask(SIG_SETMASK, &set, NULL);//设置信号掩码,线程屏蔽SIGALRM信号
        
        signal(SIGALRM, sig_alrm);  //添加接受SIGALRM的处理函数
        signal(SIGINT, sig_init);   //添加接受SIGINT的处理函数
        sigwait(&set, &sig);//sigwait只是从未决队列中删除该信号,并不改变信号掩码。也就是,当sigwait函数返回,它监听的信号依旧被阻塞。
        switch(sig){
        case 14:
            printf("sigwait, receive signal SIGALRM\n");
            /*do the job when catch the sigwait*/
            break;
        default:
            break;
        }
        sigdelset(&set, SIGALRM);     //从set集合删除SIGALRM
        pthread_sigmask(SIG_SETMASK, &set, NULL);   //重新设置信号掩码,不屏蔽SIGALRM,可以让signal正确处理信号
 
        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)中持有锁。
■ 请勿在调用模块外且可能重进入模块的函数时持有锁。
■ 一般情况下,请先使用粗粒度锁定方法,确定瓶颈,并在必要时添加细粒度锁定来缓解
瓶颈。大多数锁定都是短期持有,而且很少出现争用。因此,请仅修复测得争用的那些
锁定。
■ 使用多个锁定时,通过确保所有线程都按相同的顺序获取锁定来避免死锁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值