Linux多线程操作pthread 您所在的位置:网站首页 linux启动线程任务函数是什么 Linux多线程操作pthread

Linux多线程操作pthread

2023-09-09 13:07| 来源: 网络整理| 查看: 265

目录

进程概念

线程概念

线程进程基本操作

一、创建线程

二、线程属性

三、线程终止

四、线程安全

五、其他操作

进程概念

进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。

在Mac、Windows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的基本单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。

线程概念

线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。如果把进程理解为在逻辑上操作系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。线程可以在处理器上独立调度执行,这样,在多处理器环境下就允许几个线程各自在单独处理器上进行。操作系统提供线程就是为了方便而有效地实现这种并发性。

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。

引入线程的好处 (1)易于调度。 (2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。 (3)开销少。创建线程比创建进程要快,所需开销很少。。 (4)利于充分发挥多处理器的功能。通过创建多线程进程(即一个进程可具有两个或更多个线程),每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。

进程: 子进程具备自己独立的用户空间(内容全部复制父进程); 父子进程不可相互访问对方资源;线程: 仅申请自己的栈空间,与同进程的其它线程共享内存空间; 需要注意资源的同步和互斥访问问题

在Linux系统中,多线程的管理使用 pthread_t

线程进程基本操作 功能进程线程创建fork()pthread_create()退出exitpthread_exit()等待wait/waitpid()pthread_join()取消abort()pthread_cancel()获取IDgetpid()pthread_self()调度策略SCHED_OTHER、SCHED_FIFO、SCHED_RRSCHED_OTHER、SCHED_FIFO、SCHED_RR通信机制管道、消息队列、共享内存、信号、信号量信号、信号量、互斥锁、读写锁、条件变量 一、创建线程 pthread_create int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)

第一个参数为指向线程标识符的指针,也就是线程对象的指针

第二个参数用来设置线程属性。

第三个参数是线程运行函数的地址,通俗理解线程要执行函数(线程做的事情的)指针。一般这个函数执行时间比较长(有while循环),做的事情比较多。如果单次动作(执行时间比较短),也就无需多线程执行了。

最后一个参数是线程要运行函数的参数。

线程的默认堆栈大小是1MB,就是说,系统每创建一个线程就要至少提供1MB的内存,那么,创建线程失败,极有可能就是内存不够用了。

pthread_create会导致内存泄露! pthread_create创建的线程结束后,系统并未回收其资源,从而导致了泄露。

为什么要分离线程?

    线程的分离状态决定一个线程以什么样的方式来终止自己。线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。

从上面的描述中可以得知如果调用pthread_create函数创建一个默认非分离状态的线程,如果不用pthread_join()函数,线程结束时并不算终止,所以仍然会占用系统资源。这里有如下几种方法解决这个问题: 如果新线程创建后,不用pthread_join()等待回收新线程,那么就会造成内存泄漏,但是当等待新线程时,主线程就会一直阻塞,影响主线程处理其他链接要求,这时候就需要一种办法让新线程退出后,自己释放所有资源,因此产生了线程分离。

1.使用pthread_join()函数回收相关内存区域。

int pthread_detach(pthread_t thread);将已经运行中的线程设定为分离状态; pthread_t tid; void* state; pthread_create(&tid, NULL, test, NULL); pthread_join(tid, &state);

pthread_join使一个线程等待另一个线程结束。代码中如果没有pthread_join 主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。pthread_join()函数以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

2.可以调用 pthread_detach() 函数分离线程。

pthread_t tid; pthread_create(&tid, NULL, test, NULL); pthread_detach(tid);

当然,也可以在 thread function 中调用。

void* test(void* arg) { ..... pthread_detach(pthread_self()); return NULL; }

3.使用线程属性 pthread_attr_setdetachstate

pthread_attr_t attr; pthread_t tid; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&tid, &attr, test, NULL); sleep(3);//等待线程结束 pthread_attr_destroy(&attr); 二、线程属性

pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项:

typedef struct { int detachstate; //线程的分离状态 int schedpolicy; //线程调度策略 struct sched_param schedparam; //线程的调度参数 int inheritsched; //线程的继承性 int scope; //线程的作用域 size_t guardsize; //线程栈末尾的警戒缓冲区大小 int stackaddr_set; void * stackaddr; //线程栈的位置 size_t stacksize; //线程栈的大小 }pthread_attr_t; __detachstate, 表示新线程是否与进程中其他线程脱离同步,缺省为 PTHREAD_CREATE_JOINABLE状态。如果置位,则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为 PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到 PTHREAD_CREATE_JOINABLE状态。 int pthread_attr_setdetachstate (pthread_attr_t *__attr, int __detachstate)设定线程的分离状态 int pthread_attr_getdetachstate (const pthread_attr_t *__attr,int *__detachstate)获取线程参数中的分离状态; __schedpolicy,表示新线程的调度策略,主要包括 SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和 SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过 pthread_setschedparam()来改变。 int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy),设定线程的调度策略; __schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR 或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。 int pthread_attr_getschedparam (const pthread_attr_t *__restrict __attr, struct sched_param *__restrict __param) //获取参数中的线程优先级; int pthread_attr_setschedparam (pthread_attr_t *__restrict __attr, const struct sched_param *__restrict __param) //设定线程的优先级; __inheritsched, 有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和 调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED int pthread_attr_setinheritsched (pthread_attr_t *__attr, int __inherit) //设定线程调度策略的继承属性,该函数必须在root权限下调用; __scope, 表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同 进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。 int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope); //设定线程优先级的可竞争范围:

pthread_attr_t结构中还有一些值,但不使用pthread_create()来设置。

int pthread_attr_init (pthread_attr_t *__attr), 初始化pthread创建参数;

为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_get---/pthread_attr_set---函数。

三、线程终止 线程退出 void pthread_exit(void *value_ptr); //在线程执行的函数中调用此接口 例子: void *thread_run(void* arg) { while(1) { printf("new thread,thread is :%u,pid:%d\n",pthread_self(),getpid()); sleep(1); pthread_exit(NULL); } } 线程取消 int pthread_cancel(pthread_t thread); 参数 thread:线程ID 返回值:成功返回0;失败返回错误码 例子: int main() { pthread_t tid; pthread_create(&tid,NULL,thread_run,NULL); while(1) { printf("main thread,thread is :%u,pid:%d\n",pthread_self(),getpid()) ; sleep(3); pthread_cancel(pthread_self());//杀死自己,pthread_self()是获取主线程id } return 0; } int pthread_setcancelstate(int state, int *oldstate) state: 欲设置的线程状态,PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE。默认值是PTHREAD_CANCEL_ENABLE,可以被取消。oldstate: 存储原来的线程状态 int pthread_setcanceltype(int type, int *oldtype) type: 欲设置的类型,PTHREAD_CANCEL_DEFERRED:在取消点取消和PTHREAD_CANCEL_ASYNCHRONOUS:可随时执行新的或未决的请求。oldtype: 存储原来的类型 四、线程安全

多个线程同时操作临界资源而不会出现数据二义性,实现线程安全:

互斥: 临界资源同一时间唯一访问,是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性,操作的独占性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。同步: 临界资源的合理访问,是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。

多线程的同步与互斥的区别

假如把整条道路看成是一个【进程】的话,那么马路中间白色虚线分隔开来的各个车道就是进程中的各个【线程】了 ①这些线程(车道)共享了进程(道路)的公共资源(土地资源)。 ②这些线程(车道)必须依赖于进程(道路),也就是说,线程不能脱离于进程而存在(就像离开了道路,车道也就没有意义了)。 ③这些线程(车道)之间可以并发执行(各个车道你走你的,我走我的),也可以互相同步(某些车道在交通灯亮时禁止继续前行或转弯,必须等待其它车道的车辆通行完毕)。 ④这些线程(车道)之间依靠代码逻辑(交通灯)来控制运行,一旦代码逻辑控制有误(死锁,多个线程同时竞争唯一资源),那么线程将陷入混乱,无序之中。 ⑤这些线程(车道)之间谁先运行是未知的,只有在线程刚好被分配到CPU时间片(交通灯变化)的那一刻才能知道。

1.互斥

互斥如何实现: 互斥锁: 一个1/0的计数器 1标识完成加锁,加锁就是计数-1; 操作完毕后要解锁, 解锁就是计数+1 0表示不可以加锁, 不能加锁则等待

互斥锁的操作步骤:

1.定义互斥锁变量 pthread_mutex_t

pthread_mutex_t lock;

2.初始化互斥锁变量 pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

3.加锁 pthread_mutex_lock

int pthread_mutex_trylock(pthread_mutex_t* mutex);//非阻塞式加锁 int pthread_mutex_lock(pthread_mutex_t* mutex)//阻塞式加锁

如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被 另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。

4.解锁 pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t* mutex)//解锁

5.销毁互斥锁 pthread_mutex_destory

int pthread_mutex_destroy(pthread_mutex_t* mutex);

死锁::对所资源的竞争以及进程/线程加锁的推进顺序不当,因为对一些无法加锁的锁进行加锁而导致程序卡死

死锁产生的四个必要条件:

互斥条件(我能操作别人不能操作)不可剥夺操作(我的锁,别人不能解)请求与保持条件(拿着碗里的,看着锅里的)环路等待条件

避免死锁:破坏必要条件

死锁处理:死锁检测算法 ,银行家算法

2.同步

条件变量:描述某些资源就绪与否的状态,为了实现同步而引入。同步是以互斥为前提的。少数情况可实现无锁同步。

1.定义条件变量 pthread_cond_t

pthread_cond_t condition;

2.初始化条件变量 pthread_cond_init

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 参数: pthread_cond_attr是用来设置pthread_cond_t的属性,当传入的值是NULL的时候表示使用默认的属性。

3.等待 / 唤醒 pthread_cond_wait / pthread_cond_signal

int pthread_cond_broadcast(pthread_cond_t* cond);//唤醒条件变量等待的所有线程 int pthread_cond_signal(pthread_cond_t* cond)//唤醒条件变量上等待的一个线程 int pthread_cond_wait(pthread_cond_t* cond)//解锁互斥量并等待条件变量触发 int pthread_cond_timewait(pthread_cond_t* cond,int abstime)//pthread_cond_wait,但可设定等待超时

4.销毁条件变量 pthread_cond_destroy

int pthread_cond_destroy(pthread_cond_t* cond);

条件变量为什么要搭配互斥锁使用? 因为条件变量本身只提供等待与唤醒的功能,具体要什么时候等待需要用户来进行判断.这个条件的判断,通常涉及临界资源的操作(其他线程要通过修改条件,来促使条件满足), 而这个临界资源的操作应该受到保护.因此要搭配互斥锁一起使用。

3.互斥和同步的联合使用

pthread_mutex_lock(&mutex) while or if(线程执行的条件是否成立) pthread_cond_wait(&cond,&mutex); 线程执行 pthread_mutex_unlock(&mutex); 五、其他操作

进程id:

这里所说的进程ID指我们通过fork创建子进程,子进程和父进程在内核中独立运行,并且一个进程对应一个进程描述符(PCB),PCB中包含了进程的ID,通过getpid返回当前进程ID

线程id:

内核态线程id:linux内核中,并不存在线程这一说,而是通过复制了进程的PCB作为标识自己(线程),作为进程的一个执行分支;既然有进程描述符(PCB)标识,自然就有一个标识符(ID)来标识着我是你(进程)的哪一个分支,这个标识符(ID)就是内核中的线程ID,通过syscall获得 #include pid_t tid; tid = syscall(SYS_gettid); //在线程执行的函数中调用此接口 用户态线程id:对线程的操控是由用户自己来完成,那么对此线程操控,用户知道你是哪一个线程,故此又有了用户态的线程ID;这里我们通过pthread_self()函数获得。 #include pthread_t pthread_self(void); //在线程执行的函数中调用此接口 返回值:成功返回0,失败返回错误码 注:这里的ID是一个地址,而不是向上面两个ID是一个整数

对于单线程的进程,内核中tid==pid,对于多线程进程,他们有相同的pid,不同的tid。tid用于描述内核真实的pid和tid信息。

pthread_self返回的是posix定义的线程ID,man手册明确说明了和内核线程tid不同。它只是用来区分某个进程中不同的线程,当一个线程退出后,新创建的线程可以复用原来的id。

描述线程的id,为什么需要两个不同的ID呢?

答:这是因为线程库实际上由两部分组成:内核的线程支持+用户态的库支持(glibc),Linux在早期内核不支持线程的时候glibc就在库中(用户态)以纤程(就是用户态线程)的方式支持多线程了,POSIX thread只要求了用户编程的调用接口对内核接口没有要求。linux上的线程实现就是在内核支持的基础上以POSIX thread的方式对外封装了接口,所以才会有两个ID的问题。

gettid 获取的是内核中真实线程ID,  对于多线程进程来说,每个tid实际是不一样的。

而pthread_self获取的是相对于进程的线程控制块的首地址, 只是用来描述统一进程中的不同线程



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有