Linux多线程编程

一番码客 : 挖掘你关心的亮点。
http://efonfighting.imwork.net

1 - 线程创建

pthread_create

创建一个线程,函数的声明:

int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,
                   void* (*start_routine)(void*), void* arg)
  • para:
    • thread_out:创建线程后的标识符。
    • attr: 设置线程属性。传NULL为默认属性。
    • start_routine:线程运行函数的函数指针。
    • arg:运行函数的参数,不使用参数则为NULL。
  • return:
    • 0:创建成功。
    • 非0:创建失败,常见错误返回代码EAGAIN(统限制创建新的线程,例如线程数目过多)和EINVAL(线程属性值非法)。

pthread_t

定义在 pthreadtypes.h

typedef unsigned long int pthread_t;

线程的标识符。也就是前面创建线程时候传入的参数。

2 - 线程属性设置

pthread_attr_t

属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。pthread_attr_t结构的定义,定义在pthread.h

typedef struct
{
    uint32_t flags;
    void * stack_base;
    size_t stack_size;
    size_t guard_size;
    int32_t sched_policy;
    int32_t sched_priority;
} pthread_attr_t;

属性设置

  • 初始化:pthread_attr_init
  • 默认属性: 非绑定、非分离、1M堆栈、与父进程同样级别的优先级(一般为0)。
  • 设置:属性值只能通过相关函数(**pthread_attr_set×××**)进行设置,这个函数必须在pthread_create函数之前调用。
  • 属性操作函数:
/*
    功能:初始化一个线程对象的属性
    返回值:若是成功返回0,否则返回错误的编号
    形参:attr指向一个线程属性的指针
    说明:Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。pthread_attr_init实现时为属性对象分配了动态内存空间。
*/
int pthread_attr_init(pthread_attr_t *attr);


/* 
    功能:销毁一个线程属性对象
    返回值:若是成功返回0,否则返回错误的编号
    形参:attr指向一个线程属性的指针
    说明:经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。
*/
int pthread_attr_destroy(pthread_attr_t *attr);
    
3、获取线程间的CPU亲缘性
int pthread_attr_getaffinity_np(pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
    说  明:获取线程的CPU亲缘属性
    头文件:#include <pthread.h>
4、设置线程的CPU亲缘性
int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr         指向一个线程属性的指针
            cpusetsize   指向CPU组的缓冲区大小
            cpuset       指向CPU组的指针
    说  明:通过指定cupset来设置线程的CPU亲缘性
    头文件:#include <pthread.h>
5、获取线程分离状态属性
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr          指向一个线程属性的指针
            detachstate   保存返回的分离状态属性
    说  明:获取线程分离状态属性
    头文件:#include <pthread.h>
6、修改线程分离状态属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr        指向一个线程属性的指针
            detachstat  有两个取值
                        PTHREAD_CREATE_DETACHED(分离)
                        PTHREAD_CREATE_JOINABLE(非分离)
    说  明:Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。
    头文件:#include <pthread.h>
7、获取线程的栈保护区大小
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            guardsize  返回获取的栈保护区大小
    说  明:获取线程的栈保护区大小
    头文件:#include <pthread.h>
8、设置线程的栈保护区大小
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            guardsize  线程的栈保护区大小
    说  明:参数提供了对栈指针溢出的保护。
            默认为系统页大小
            如果设置为0表示没有保护区。
            大于0,则会为每个使用 attr 创建的线程提供大小至少为 guardsize 字节的溢出保护区
    头文件:#include <pthread.h>
9、获取线程的作用域
int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            scope      返回线程的作用域
    说  明:指定了作用域也就指定了线程与谁竞争资源
    头文件:#include <pthread.h>
10、设置线程的作用域
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            guardsize  线程的作用域,可以取如下值
                       PTHREAD_SCOPE_SYSTEM    与系统中所有进程中线程竞争
                       PTHREAD_SCOPE_PROCESS   与当前进程中的其他线程竞争
    说  明:指定了作用域也就指定了线程与谁竞争资源
    头文件:#include <pthread.h>
11、获取线程的堆栈信息(栈地址和栈大小)
int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            stackaddr  返回获取的栈地址
            stacksize  返回获取的栈大小
    说  明:获取线程的堆栈地址和大小
    头文件:#include <pthread.h>
12、设置线程堆栈区
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            stackaddr  线程的堆栈地址:应该是可移植的,对齐页边距的
                       可以用posix_memalign来进行获取
            stacksize  线程的堆栈大小:应该是页大小的整数倍
    说  明:设置堆栈区,将导致pthread_attr_setguardsize失效。
    头文件:#include <pthread.h>
13、获取线程堆栈地址
int pthread_attr_getstackaddr(pthread_attr_t *attr, void **stackaddr);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            stackaddr  返回获取的栈地址
    说  明:函数已过时,一般用pthread_attr_getstack来代替
    头文件:#include <pthread.h>
14、设置线程堆栈地址
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            stackaddr  设置线程堆栈地址
    说  明:函数已过时,一般用pthread_attr_setstack来代替
    头文件:#include <pthread.h>
15、获取线程堆栈大小
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            stacksize  返回线程的堆栈大小
    说  明:获取线程堆栈大小
    头文件:#include <pthread.h>
16、设置线程堆栈大小
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            guardsize  线程的栈保护区大小:应该是页大小的整数倍
    说  明:设置线程堆栈大小:
    头文件:#include <pthread.h>
17、获取线程的调度策略
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            policy     返回线程的调度策略
    说  明:获取线程的调度策略
    头文件:#include <pthread.h>
18、设置线程的调度策略
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            policy     线程的调度策略,有如下三种:
                        SCHED_FIFO    先入先出策略
                        SCHED_RR      轮转调度,类似于 FIFO,但加上了时间轮片算法
                        SCHED_OTHER      系统默认策略
    说  明:设置线程的调度策略
    头文件:policy#include <pthread.h>
19、获取线程的调度参数
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            param      返回获取的调度参数,该结构仅有一个从参数,如下
                        struct sched_param 
                        {
                            int sched_priority; /* Scheduling priority */
                        };
    说  明:获取线程的调度参数
    头文件:#include <pthread.h>
20、设置线程的调度参数
int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            param      要设置的调度参数
    说  明:设置线程的调度参数
    头文件:#include <pthread.h>
21、获取线程是否继承调度属性
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr          指向一个线程属性的指针
            inheritsched  返回继承调度属性的设置
    说  明:获取线程是否继承调度属性
    头文件:#include <pthread.h>
22、设置线程是否继承调度属性
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
    返回值:若是成功返回0,否则返回错误的编号
    形  参:
            attr       指向一个线程属性的指针
            guardsize  设置线程是否继承调度属性
                        PTHREAD_INHERIT_SCHED:调度属性将继承于正创建的线程
                                               attr中的设置将被忽略
                        PTHREAD_EXPLICIT_SCHED 调度属性将被设置为attr中指定的属性值
    说  明:
    头文件:#include <pthread.h>

线程参数传递

参数传递的是指针。代码中将value的值传入。

pthread_create(&id, &attr, run, &value);

然后进行指针变量类型转换就可得到值。

int value=*(int *)ptr;

Linux内核的三种调度策略

1,SCHED_OTHER:分时调度策略。

它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。

2,SCHED_FIFO:实时调度策略,先到先服务。一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。

它是一种实时的先进先出调用策略,且只能在超级用户下运行。这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程J。此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部。当所有高优先级的线程终止或者阻塞时,它将被运行。对于相同优先级别的线程,按照简单的先进先运行的规则运行。我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。

3,SCHED_RR:实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。

鉴于SCHED_FIFO调度策略的一些缺点,SCHED_RR对SCHED_FIFO做出了一些增强功能。从实质上看,它还是SCHED_FIFO调用策略。它使用最大运行时间来限制当前进程的运行,当运行时间大于等于最大运行时间的时候,当前线程将被切换并放置于相同优先级队列的最后。这样做的好处是其他具有相同级别的线程能在“自私“线程下执行。

系统创建线程时,默认的线程是SCHED_OTHER。所以如果我们要改变线程的调度策略的话,可以通过int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)这个函数实现:

线程优先级

主要涉及sched_parampthread_attr_setschedparampthread_attr_getschedparam等方法。

优先级变量存放在结构sched_param中。用pthread_attr_getschedparampthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。

首先,可以通过以下两个函数来获得线程可以设置的最高和最低优先级,函数中的策略即上述三种策略的宏定义:

int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);

SCHED_OTHER是不支持优先级使用的,而SCHED_FIFO和SCHED_RR支持优先级的使用,他们分别为1和99,数值越大优先级越高。

设置和获取优先级通过以下两个函数:

int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);

上面的param使用了下面的这个数据结构:

struct sched_param
{
    int __sched_priority; //所要设定的线程优先级
};

例如:

 param.sched_priority = 51; //设置优先级

线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。
在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

3 - 线程的数据处理

在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。

线程数据和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。也就是说,我们要在线程中使用全局变量,但是这个全局变量在各个线程中是独立的。

例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。

要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。

相关的函数和结构:

  • **pthread_key_t key ** :

    指向一个键值的指针 pthread_key_t的定义为typedef int pthread_key_t;不论哪个线程调用了

  • **destructor_function ** :
    这是一个销毁函数,它是可选的,可以为 NULL,为 NULL 时,则系统调用默认的销毁函数进行相关的数据注销。如果不为空,则在线程退出时(调用 pthread_exit() 函数)时将以 key 锁关联的数据作为参数调用它,以释放分配的缓冲区,或是关闭文件流等。

  • **pthread_setspecific/pthread_getspecific ** :
    设置和获取线程变量的值。

4 - 线程的同步和互斥:

互斥锁

互斥锁用来保证一段时间内只有一个线程在执行一段代码。一般使用流程:

  • 定义一个锁(pthread_mutex_t)
  • 初始化锁(pthread_mutex_init)
  • 使用pthread_mutex_lock/pthread_mutex_unlock进行锁定和解锁。

注意:加锁、解锁之间不能return或break,否则会死锁。

信号量

信号量是一种特殊的变量,本质上是一个非负的整数计数器,可以被增加或减少,但系统保证对该变量的访问是原子操作(这能控制多个线程操作同一资源时的顺序问题)。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。

这里需要引入新的头文件semaphore.h

  • sem_init 初始化信号量。该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.
  • sem_post ( sem_t *sem ) 该函数用于以原子操作的方式将信号量的值加1。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞。
  • sem_wait( sem_t *sem ) 被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减1,表明公共资源经使用后减少。
  • sem_destroy 该函数用于清理用完的信号量。

条件变量

互斥锁是用来给资源上锁的,而条件变量是用来等待而不是用来上锁的。

条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。

通常条件变量和互斥锁同时使用。

和条件变量使用有关的几个重要函数:

/*
初始化与销毁:
条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:
- 静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.
- 动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.
*/
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
int pthread_cond_destroy(pthread_cond_t *cond);

/*
等待条件:
等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数. 函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.
当pthread_cond_wait返回时, 互斥量再次被锁住.
*/
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

/*通知条件:
这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号.
必须注意, 一定要在改变条件状态以后再给线程发送信号.
*/
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞
参考
  • https://www.ibm.com/developerworks/cn/linux/l-cn-mthreadps/
  • https://cloud.tencent.com/developer/article/1193996
  • https://blog.csdn.net/zsf8701/article/details/7843837
  • Linux 线程调度与优先级: https://www.cnblogs.com/xiaojianliu/p/9689118.html
  • Linux线程同步——条件变量:https://www.cnblogs.com/liangf27/p/9493722.html

免费知识星球: 一番码客-积累交流
微信公众号:一番码客
微信:Efon-fighting
网站: http://efonfighting.imwork.net

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一番码客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值