…
由于进程的地址空间是私有的因此在进程间上下文切换时,系统开销比较大;
为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程;
在同一个进程中创建的线程共享该进程的地址空间;
Linux里同样用task_struct来描述一个线程。线程和进程都参与统一的调度;
◆ 可执行的指令
◆ 静态数据
◆ 全局变量
◆ 堆
◆ 进程中打开的文件描述符
◆ 信号处理函数
◆ 当前工作目录
◆ 用户ID
◆ 用户组ID
◆ 线程ID (TID)
◆ PC(程序计数器)和相关寄存器
◆ 栈
◆ 局部变量
◆ 返回地址
◆ 错误号 (errno)
◆ 信号掩码和优先级
◆ 执行状态和属性
线程是一个轻量级的进程;
线程运行需要用到的系统资源(cpu,内存)都是进程给它分配的;
进程是操作系统分配资源的最小单位;
线程是操作系统运行的最小单位;
创建线程的目的:为了解决在同一个进程中并发地处理多个任务
./QQ 只能发信息,其它功能都没有
不爽,功能太少,我要听歌,视频
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
参数:thread ----》线程的id 系统分配
attr ----》 线程的属性:可分离属性和不可分离属性,一般情况下设置为NULL,表示使用系统默认的属性(不可分离)
void *(*start_routine) (void *)----》线程的功能函数,你创建线程需要完成的任务就靠它来实现
arg ----》传递给线程功能函数的参数
注意:
千万不要把线程的创建理解为函数调用(线程它就是进程中一个独立运行的小单元)
既然线程是独立运行的小单元,在主线程中如果没有使用线程的回收函数,就必须让主线程稍微延时一下,等子线程有充足的时间去完成代码。
补充:*((int *)arg) 将空类型的指针arg强转成int * -----》然后解引用
((void *)&(a)) 将整数a取地址 ----》变成指针了----》然后再将该指针强转成void *类型
#include
void pthread_exit(void *retval);
参数:retval ----》线程退出是状态信息
返回值:void
#include
int pthread_join(pthread_t thread, void **retval);
参数:thread ----》你要回收的那个线程的id
**retval ----》保存线程退出时的状态信息
返回值:0成功
-1失败
#include
int pthread_cancel(pthread_t thread);
参数:thread ----》你要取消的那个线程的id号
返回值:0成功
-1失败
线程的取消分两种状态:
可取消
分为两种类型: 立即取消,适当的时候取消
不可取消
设置线程是否可取消状态:pthread_setcancelstate()
#include
int pthread_setcancelstate(int state, int *oldstate);
参数:state----》可取消:PTHREAD_CANCEL_ENABLE
不可取消: PTHREAD_CANCEL_DISABLE
int pthread_setcanceltype(int type, int *oldtype);
参数:type----》立即:PTHREAD_CANCEL_ASYNCHRONOUS
立马退出,没有商量的余地
适当:PTHREAD_CANCEL_DEFERRED
在你取消之后会执行下一句代码之后再退出(执行一次)
不可取消
#include
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
#include
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
参数:pthread_attr_t attr ----》线程的属性结构体
Detach state = PTHREAD_CREATE_JOINABLE ;分离状态
Scope = PTHREAD_SCOPE_SYSTEM;作用域
Inherit scheduler = PTHREAD_INHERIT_SCHED;调度器
Scheduling policy = SCHED_OTHER;调度策略 轮询,抢占式
Scheduling priority = 0;优先级
Guard size = 4096 bytes
Stack address = 0x40196000;栈地址
Stack size = 0x201000 bytes;栈内存的大小
detachstate ----》使用宏来表示分离和不可分离状态
PTHREAD_CREATE_DETACHED 可分离
PTHREAD_CREATE_JOINABLE 不可分离
线程编译:gcc .c -o c -pthread
线程优先级可以由线程属性控制,linux 内核提供了很多函数操作 pthread_attr_t 结构体,修改后将该结构体 通过 函数pthread_create 的第二个参数传递给线程即可
SCHED_OTHER(0):分时调度策略
线程默认调度策略,不区分优先级,该调度方式通过分时来完成的。当线程处于这种调度策略时,对线程进行优先级设置会失败。但高优先级的线程可抢占处于该调度策略的线程。
SCHED_FIFO(1):实时调度策略
先进先出原则,这种调度方式有优先级之分,并且无时间片概念,处于该调度策略时,高优先级的进程将会一直占用CPU直到有更高优先级的线程出现,将线程设置为该调度策略的时候需要超级用户模式。
SCHED_RR(2):实时调度策略
时间片轮转,当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平
线程优先级可以由线程属性控制,linux 内核提供了很多函数操作 pthread_attr_t 结构体,修改后将该结构体 通过 函数pthread_create 的第二个参数传递给线程即可
1、线程调度策略设置和获取函数,pthread_attr_t为线程属性,policy是线程调度策略
int pthread_attr_setschedpolicy(pthread_attr_*, int policy)
int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy)
2、线程优先级设置和获取函数
int pthread_attr_setschedparam(pthread_attr_t *,const struct sched_param *);
int pthread_attr_getschedparam(const pthread_attr_t *,struct sched_param *);
结构体 sched_param 中只有一项 __sched_priority 代表优先级,值越大优先级越高
3、获取系统调度策略的最大最小优先级
int sched_get_priority_max( int policy );
int sched_get_priority_min( int policy );
4、获取和设置线程的继承性 默认是 PTHREAD_INHERIT_SCHED
int pthread_attr_getinheritsched(const pthread_attr_t * attr,int *inheritsched);
int pthread_attr_setinheritsched(pthread_attr_t * attr,int inheritsched);
5、线程绑定CPU
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);
cpu_set_t这个结构体的理解类似于select中的fd_set,可以理解为cpu集,通过约定好的宏来进行清除、设置以及判断:
//初始化,设为空
void CPU_ZERO (cpu_set_t *set);
//将某个cpu加入cpu集中
void CPU_SET (int cpu, cpu_set_t *set);
//将某个cpu从cpu集中移出
void CPU_CLR (int cpu, cpu_set_t *set);
//判断某个cpu是否已在cpu集中设置了
int CPU_ISSET (int cpu, const cpu_set_t *set);
演示代码
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void *Thread1(void) {
while(1)
{
printf("This is the 1th\n");
sleep(1);
}
}
void *Thread2(void) {
struct timeval tv;
while(1){
gettimeofday(&tv,NULL);
printf("change priority befor millisecond:%ld\n",tv.tv_sec*1000 + tv.tv_usec/1000);
sleep(1);
}
}
void Thread3(void) {
struct timeval tv;
while(1) {
gettimeofday(&tv, NULL);
printf("change priority after millisecond:%ld\n",tv.tv_sec*1000 + tv.tv_usec/1000);
sleep(1);
}
}
void *thread_func(void *arg) {
cpu_set_t cpuset;
int policy, ret;
struct sched_param param;
//获取线程调度参数
ret = pthread_getschedparam(pthread_self(), &policy, ¶m);
if(ret != 0) {
printf("pthread_getschedparam %s\n", strerror(ret) );
exit(1);
}
if (SCHED_FIFO == policy) {
printf("policy:SCHED_FIFO\n");
}
else if (SCHED_OTHER == policy) {
printf("policy:SCHED_OTHER\n");
}
else if (SCHED_RR == policy) {
printf("policy:SCHED_RR\n");
}
printf("thread_func priority is %d\n", param.sched_priority);
CPU_ZERO(&cpuset);
CPU_SET(1, &cpuset); /* cpu 1 is in cpuset now */
/* bind process to processor 1 */
if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) !=0) {
perror("pthread_setaffinity_np");
}
printf("Core 1 is running! this pthread PID is %d\n",gettid());
Thread3();
}
int main(int argc, char *argv[]) {
pthread_t my_thread, id1,id2;
pthread_attr_t attr;
struct sched_param sp;
int policy, inher;
time_t startwtime, endwtime;
int ret = 0, rs = 0;
bzero((void*)&sp, sizeof(sp));
pid_t pid = gettid();
pthread_t tid = pthread_self();
printf("main: pid=%d, tid=%lu\n", pid, tid);
/* 在默认情况下通过pthread_create函数创建的线程是非分离属性的,
* 由pthread_create函数的第二个参数决定,
* 在非分离的情况下,当一个线程结束的时候,
* 它所占用的系统资源并没有完全真正的释放,也没有真正终止。
* 只有在pthread_join函数返回时,该线程才会释放自己的资源。
* 或者是设置在分离属性的情况下,一个线程结束会立即释放它所占用的资源。
* pthread_join()函数会一直阻塞调用线程,直到指定的线程终止。
* 当pthread_join()返回之后,应用程序可回收与已终止线程关联的任何数据存储空间。
* 但是,同时需要注意,一定要和上面创建的某一线程配套使用,这样还可以起到互斥的作用。
* 否则多线程可能抢占CPU资源,导致运行结果不确定。
*/
rs = pthread_attr_init(&attr);
assert(rs == 0);
//获取继承的调度策略
ret = pthread_attr_getinheritsched(&attr, &inher);
if (ret!=0) {
printf("pthread_attr_getinheritsched\n%s\n", strerror(ret));
exit(1);
}
//
if (inher == PTHREAD_EXPLICIT_SCHED) {
printf("PTHREAD_EXPLICIT_SCHED\n");
}
else if (inher == PTHREAD_INHERIT_SCHED) {
printf("PTHREAD_INHERIT_SCHED\n");
inher = PTHREAD_EXPLICIT_SCHED;
}
//设置继承的调度策略
//必需设置inher的属性为 PTHREAD_EXPLICIT_SCHED,否则设置线程的优先级会被忽略
ret = pthread_attr_setinheritsched(&attr, inher);
if (ret!=0)
{
printf("pthread_attr_setinheritsched\n%s\n", strerror(ret));
exit(1);
}
policy = SCHED_RR; //需要超级用户权限
pthread_attr_setschedpolicy( &attr, policy );//设置 调度策略为FIFO
assert( rs == 0 );
const int priority = 51; //设置优先级 为51
sp.__sched_priority = priority;
if(pthread_attr_setschedparam(&attr, &sp) != 0){//设置优先级
printf("pthread set sched priority failed\n");
}
//创建子线程
if (pthread_create(&my_thread, &attr, thread_func, NULL) != 0) {
perror("pthread_create failed\n");
}
ret = pthread_create(&id1, NULL, (void*)Thread1, NULL);
if(ret) {
printf("cread pthread1 failed\n ");
return -1;
}
ret = pthread_create(&id2, NULL, (void*)Thread2, NULL);
if(ret) {
printf("cread pthread2 failed\n ");
return -1;
}
//回收子线程
pthread_join(my_thread, NULL);
pthread_join(id1, NULL);
pthread_join(id2, NULL);
return 0;
}
多线程共享同一个进程的地址空间**;**
优点:线程间很容易进行通信;
缺点:线程间数据交互容易互相干扰;
信号量代表某一类资源,其值表示系统中该资源的数量;
信号量的值为非负整数。
1、信号量特点
信号量是一个受保护的变量,只能通过三种操作来访问
◆初始化
◆P操作(申请资源)
当信号量的值等于0时, 该操作将可能引起线程睡眠
当信号量的值大于0时, 该操作将会使得其值减1
◆V操作(释放资源)
当当前没有线程正在等待该信号量时, 该操作将使得其值加1
当当前有线程正在等待该信号量时, 该操作将唤醒该线程
2、信号量相关操作函数
*sem_t----》信号量的类型*
(1)信号量的初始化sem_init()
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
Link with -lrt or -pthread.
参数: sem_t ----》存放待初始化信号量的变量类型
pshared ---》信号量共享的范围(0: 线程间使用 非0:进程间使用)
value ----》信号量的初值
返回值:0成功 -1失败
(2)信号量等待(类似于p操作)sem_wait()
#include
int sem_wait(sem_t *sem);
参数:sem_t ----》上一步初始化完以后的信号量
返回值:0成功 -1失败
(3)信号量的唤醒(类似于v操作) sem_post( );
#include
int sem_post(sem_t *sem);
Link with -lrt or -pthread.
参数:sem_t ----》上一步初始化完以后的信号量
返回值:0成功 -1失败
(4)信号量的销毁sem_destroy()
#include
int sem_destroy(sem_t *sem);
小结:
不管是进程间通信还是线程间的通信:为了达到协调的目的,几乎都是采用阻塞的办法(因为你只有限制了别人的运行,你才有机会“垄断”资源),但是相应的也必须要有让阻塞解除的操作。
进程间的信号量 ----》只有信号量的值为0的时候比较奇特,为0的时候p操作会阻塞当前进程,什么时候解除呢? 另外的进程进行V操作
引入互斥(mutual exclusion)锁的目的是用来保证共享数据操作的完整性。
每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源;
线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。
1、互斥锁的特点
协调不同线程之间对于共享资源的访问;
互斥锁有上锁和解锁两种操作,如果一个线程上锁了(还没有解锁)的情况,其它线程是不能对这个锁进行上锁/解锁操作(如果进行了以上两种操作,会阻塞);
**谁上锁,谁解锁,不能交叉。**
2、线程锁相关的函数
pthread_mutex_t ----》互斥锁的类型
(1)线程锁的初始化pthread_mutex_init()
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
参数:mutex ---》锁类型的指针变量
attr ---》锁的属性 一般设置为NULL,使用默认属性
返回值:0成功 -1失败
(2)上锁pthread_mutex_lock()
#include
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:mutex ---》初始化完毕之后的互斥锁
如果有其它的线程已经对该锁上锁了,那么本线程再次上锁就会发生阻塞
返回值:0成功 -1 失败
int pthread_mutex_trylock(pthread_mutex_t *mutex);
如果有其它的线程已经对该锁上锁了,本线程就不上锁,返回,不阻塞
(3)解锁 pthread_mutex_unlock()
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:mutex ---》互斥锁
返回值:0成功 -1 失败
(4)销毁pthread_mutex_destroy()
int pthread_mutex_destroy(pthread_mutex_t *mutex);
线程锁一般配合着条件变量来操作实现效果更好
条件变量是另一种逻辑稍微复杂一点点的同步互斥机制,他必须跟互斥锁一起配合使用。
1、条件变量的特点
不可以单独使用,它是配合互斥锁一起使用的;
wait()这个函数两个作用,先解锁然后让调用它的线程阻塞;
**一旦wait()被signal()唤醒,wait()就会立马上锁,并解除阻塞**。
2、条件变量相关的接口函数
pthread_cond_t ----》条件变量类型
(1)条件变量的初始化pthread_cond_init()
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
参数:pthread_cond_t ----》条件变量类型
attr ----》设置为NULL,使用系统默认的属性
返回值:0成功 -1失败
(2)阻塞条件变量 pthread_cond_wait()
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数:cond ---》条件变量
mutex ---》互斥锁
返回值:0成功 -1失败
(3)唤醒条件变量 pthread_cond_signal()
#include
//通知所有的被阻塞的条件变量,唤醒
int pthread_cond_broadcast(pthread_cond_t *cond);
//唤醒条件变量
int pthread_cond_signal(pthread_cond_t *cond);
(4)条件变量销毁pthread_cond_destroy()
#include
int pthread_cond_destroy(pthread_cond_t *cond);
参数:cond ---》条件变量
mutex ---》互斥锁
返回值:0成功 -1失败
小结:
使用互斥锁(互相排斥)或者条件变量注意:在没有人为干扰的情况(没有sleep()函数的情况下),线程间的切换是随机的,作为程序员应该去人为的按照你的逻辑去干扰一下(usleep)。
pthread_cond_wait()跟pthread_cond_signal是一对,不要弄成了其它线程根本就没有pthread_cond_signal,你就想当然地认为只要其它线程解锁了,我的wait就能解除阻塞(不对的)
实践项目源代码下载链接:linux线程池实现数据拷贝项目
【线程池实现拷贝功能】
1、使用线程池的概念,利用linux多线程对一个目录以及目录中嵌套的文件进行拷贝,保证拷贝数据的完整性,并且多线程并发进行拷贝,节约程序运行时间,提高工作效率。
2、涉及linux文件IO、linux多线程&线程池、数据结构(链表使用)、Makefile等编程知识
1、图解
2、线程池的特点:
综合了数据结构的链表,以及线程中的互斥锁,条件变量等等知识点的综合性工具
3、线程池的工作原理
(1)首先我们创建线程池的目的是为了帮助我处理多个任务的,既然是用来处理任务的:我们就想到一个办法,将各个要处理的任务封装成结构体,这个结构体理所当然必须包含最少以下三个成员变量:(通过创建一个任务链表,将所有需要处理的任务封装在这个链表中,那我是如何知道任务节点需要我做什么事情呢?很简单,任务接头体中有函数指针告诉你)
struct task
{
void *(*routine)(void *) -----》处理任务的函数,之所以该函数指针声明成这个样子,是为了增强通用性
void *arg; ----》传递给任务函数的参数
struct task *next;
}task ;
(2)第一步创建的任务链表由谁来处理呢???-----》由线程池来处理
A: 线程池究竟是什么东西呢?-----》实际上就是一个结构体(是你精心设计的结构体)
typedef struct thread_pool
{
pthread_mutex_t mymutex; 为扣节点做准备
pthread_cond_t mycond; 配合互斥锁使用
struct task *mytask; 任务类型结构体指针
unsigned long *pthreadid; 存储线程id
int curpthreadnum; 当前线程池中有多少个活动的线程
int curtasknum; 当前任务链表中有效任务节点的数量
bool shutdown; 标识线程池是否被销毁
}thread_pool ;
B: 线程的功能函数
void *process(void *arg)
{
struct thread_pool *pool2 = ( struct thread_pool *)arg;
while(1)
{
pthread_mutex_lock(&(pool2->mymutex));
while(pool2->curtasknum==0&&pool2->shutdown==false)
{
pthread_cond_wait();
}
if(pool2->curtasknum==0&&pool2->shutdown==true)
{
pthread_mutex_unlock(&(pool2->mymutex));
pthread_exit();
}
//扣节点(遍历)
(head->next)
pool2->curtasknum--;
pthread_mutex_unlock(&(pool2->mymutex));
//扣完之后调用任务链表结构体中的那个回调函数去处理该任务
(pool2->mytask->routine)( );
}
pthread_exit();
}
for(i=0 ; i<7; i++)
{
pthread_create(&pthreadid[i],NULL,process,(void *)pool)
}
(3)使用线程池代码的思路:
封装几个函数:
A: 线程池的初始化
int pool_init(thread_pool *pool , int n)
{
pthread_mutex_t mymutex;
pthread_cond_t mycond;
struct task *mytask ---->malloc分配堆空间,你创建的任务的链表的表头节点
pool-> unsigned long *pthreadid; -----》malloc分配堆空间,n*sizeof()
int curpthreadnum; n
int curtasknum; 0
bool shutdown; false
// 循环创建线程 n个
for(i=0 ; imymutex));
task *mytemtask = malloc()
mytemtask->routine = play_music; //调用回调函数play_music()
mytemtask->arg = NULL;
mytemtask->next = NULL;
// 设置了线程池能够处理的链表最大长度是200
if(pool->curtasknum>200)
{
printf() ;
pthread_mutex_unlock(&(pool->mymutex));
}
添加任务节点(添加到任务链表的结尾位置)
p =pool->mytask;
while(p->next !=NULL)
p= p->next;
mytemtask = p->next;
pool->curtasknum++;
pthread_mutex_unlock(&(pool2->mymutex));
pthread_cond_signal();
}
}
C:线程池的销毁
int pool_destroy(thread_pool *pool)
{
pool->shutdown= true;
pthread_mutex_destroy();
pthread_cond_destroy();
pool->curtasknum = 0;
free() ; //释放之前申请的堆空间
for()
pthread_join()
}
https://blog.csdn.net/weixin_44845857/article/details/118409858