linux线程变量?linux 查看线程
大家好,如果您还对linux线程变量不太了解,没有关系,今天就由本站为大家分享linux线程变量的知识,包括linux 查看线程的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!
有人能教下我有关linux里面线程的知识吗
.线程的基本介绍
(1)线程的概述
线程与进程类似,也允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程,同一程序中的所有线程共享同一份全局内存区域,线程之间没有真正意义的等级之分。同一个进程中的线程可以并发执行,如果处理器是多核的话线程也可以并行执行,如果一个线程因为等待I/O操作而阻塞,那么其他线程依然可以继续运行
(2)线程优于进程的方面
argv,environ
主线程栈
线程3的栈
线程2的栈
线程1的栈
共享函数库共享的内存
堆
未初始化的数据段
初始化数据段
文本
.进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通讯,在进程之间交换信息
.调用fork()来创建进程代价相对较高
线程很好的解决了上述俩个问题
.线程之间能够方便,快速的共享信息,只需将数据复制到共享(全局或堆)变量中即可
.创建线程比创建线程通常要快10甚至更多,线程创建之所以快,是因为fork创建进程时所需复制多个属性,而在线程中,这些属性是共享的。
(3)创建线程
启动程序时,产生的进程只有单条线程,我们称之为主线程
#include<pthread.h>
int pthread_create(pthread_t*thread,const pthread_attr_t*attr,void*(*start)(void*),void*arg);12
新线程通过调用带有arg的函数开始执行,调用pthread_create()的线程会继续执行该调用之后的语句。
(4)终止线程
可以以如下方式终止线程的运行
.线程调用pthread_exit()
.线程start函数执行return语句并返回指定值
.调用pthread_cancel()取消线程
.任意线程调用了exit(),或者主线程执行了return语句,都会导致进程中的所有线程立即终止
pthread_exit()函数可以终止线程,且其返回值可由另一线程通过调用pthread_join()获得
#include<pthread.h>void pthread_exit(void*retval);12
调用pthread_exit()相当于在线程的start函数中执行return,不同之处在于,pthread_exit()可以在任何地方调用,参数retval指定了线程的返回值
(5)获取线程ID
#include<pthread.h>pthread_t pthread_self(void);12
线程ID在应用程序中主要有如下用途
.不同的pthreads函数利用线程ID来标识要操作目标线程。
.在具体的应用程序中,以特定线程的线程ID作为动态数据结构的标签,这颇有用处,既可用来识别某个数据结构的创建者或属主线程,又可确定随后对该数据结构执行操作的具体线程
函数pthread_equal()可检查俩个线程的ID是否相同
#include<pthread.h>int pthread_equal(pthread_t t1,pthread_t t2);//如果相同返回非0值,否则返回0123
(6)连接已终止的线程
函数pthread_join()等待由thread表识的线程终止
#include<pthread.h>int pthread_join(pthread_t thread,void**retval);//返回0调用成功,否则失败123
如果pthread_join()传入一个之前已然连接过的线程ID,将会导致无法预知的行为,当相同线程ID在参与一次连接后恰好为另一新建线程所重用,再度连接的可能就是这个新线程
若线程未分离,则就应该使用pthread_join()来连接线程,否则会产生僵尸线程
pthrea_join()函数的要点
.线程之间的关系是对等的,所以任意线程都可以调用pthread_join()来连接其他线程
.pthread_join()无法针对任意线程,只能连接单个线程
(6)线程的分离
默认情况下线程都是可连接的,但有时候,我们并不关心线程退出的状态,我们可以调用pthread_detach()并向thread参数传入指定线程的的标识符,将该线程标记为处于分离状态
#include<pthread.h>int pthread_detach(pthread_t thread);//返回0成功,否则失败123
一旦线程处于分离状态,就不能在使用pthread_join()来获取其状态,也无法使其重返可连接状态
(7)在应用程序中如何来选择进程还是线程
.线程之间共享数据很简单,进程间的数据共享需要更多的投入
.创建线程要比创建进程块很多
.多线程编程时,需要确保调用线程安全的函数
.某个线程中的bug可能会危害进程中所有线程
.每个线程都在征用宿主进程中有限的虚拟地址空间
.在多线程应用中,需要小心使用信号
.除了数据,线程还可以共享文件描述符,信号处置,当前工作目录,以及用户ID和组ID
线程的同步
(1)保护共享变量访问:互斥量
线程的主要优势在于能够通过全局变量来共享信息,不过这种共享是有代价的。必须确保多个线程修改同一变量时,不会有其他线程也正在修改此变量,为避免线程更新时共享变量时所出现的问题,必须使用互斥量来确保同时仅有一个线程可以访问某项共享资源
(2)静态分配的互斥锁
互斥锁既可以像静态变量那样分配,也可以在运行时动态分配,互斥量属于pthread_mutex_t类型的变量,在使用之前必须对其初始化。对于静态分配的互斥量而言,可如下例所示,将PTHREAD_MUTEX_INITIALIZER赋给互斥量
pthread_mutex_t= PTHREAD_MUTEX_INITIALIZER;1
1.加锁和解锁互斥量
初始化之后,互斥量处于未锁定状态。函数pthread_mutex_lock()可以锁定某一互斥量
而函数pthread_mutex_unlock()则可以将一个互斥量解锁
#include<pthread.h>int pthread_mutex_lock(pthread_mutex_t*mutex);int pthread_mutex_unlock(pthread_mutex_t*mutex);//返回0成功,其他失败1234
要锁定互斥量,在调用pthread_mutex_lock()时需要指定互斥量,如果互斥量当前处于未锁定状态,则该调用将会立即返回,如果该互斥量已被其他线程锁定,那么该调用将会阻塞,直至互斥量被解锁
函数pthread_mutex_unlock()将解锁之前已遭调用线程锁定的互斥量
2.互斥量的性能
通常情况下,线程会花费更多的时间去做其他工作,对互斥量的加锁解锁相对要少的多,因此使用互斥量对大部分程序来说性能并无显著的影响
3.互斥量的死锁
当一个线程需要同时访问多个共享资源时,没个资源由不同的互斥索管理。当超过一个线程加锁同一组互斥量时,就有可能发生死锁。如下图所示
线程A
1.pthread_mutex_lock(mutex1);
2.pthread_mutex_lock(mutex2);
线程2
1.pthread_mutex_lock(mutex2);
2.pthread_mutex_lock(mutex1);
每个线程都成功的锁住一个互斥量,接着试图对以为另一线程锁定的互斥量加锁,就会一直等下去
要避免此类死锁问题,最简单的就是定义互斥量的层级关系
linux下线程属性常用操作有哪些
LinuxThread的线程机制
LinuxThreads是目前Linux平台上使用最为广泛的线程库,由Xavier Leroy(Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行。它所实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。
1.线程描述数据结构及实现限制
LinuxThreads定义了一个struct _pthread_descr_struct数据结构来描述线程,并使用全局数组变量 __pthread_handles来描述和引用进程所辖线程。在__pthread_handles中的前两项,LinuxThreads定义了两个全局的系统线程:__pthread_initial_thread和__pthread_manager_thread,并用 __pthread_main_thread表征__pthread_manager_thread的父线程(初始为 __pthread_initial_thread)。
struct _pthread_descr_struct是一个双环链表结构,__pthread_manager_thread所在的链表仅包括它一个元素,实际上,__pthread_manager_thread是一个特殊线程,LinuxThreads仅使用了其中的errno、p_pid、 p_priority等三个域。而__pthread_main_thread所在的链则将进程中所有用户线程串在了一起。经过一系列 pthread_create()之后形成的__pthread_handles数组将如下图所示:
图2 __pthread_handles数组结构
新创建的线程将首先在__pthread_handles数组中占据一项,然后通过数据结构中的链指针连入以__pthread_main_thread为首指针的链表中。这个链表的使用在介绍线程的创建和释放的时候将提到。
LinuxThreads遵循POSIX1003.1c标准,其中对线程库的实现进行了一些范围限制,比如进程最大线程数,线程私有数据区大小等等。在 LinuxThreads的实现中,基本遵循这些限制,但也进行了一定的改动,改动的趋势是放松或者说扩大这些限制,使编程更加方便。这些限定宏主要集中在sysdeps/unix/sysv/linux/bits/local_lim.h(不同平台使用的文件位置不同)中,包括如下几个:
每进程的私有数据key数,POSIX定义_POSIX_THREAD_KEYS_MAX为128,LinuxThreads使用 PTHREAD_KEYS_MAX,1024;私有数据释放时允许执行的操作数,LinuxThreads与POSIX一致,定义 PTHREAD_DESTRUCTOR_ITERATIONS为4;每进程的线程数,POSIX定义为64,LinuxThreads增大到1024(PTHREAD_THREADS_MAX);线程运行栈最小空间大小,POSIX未指定,LinuxThreads使用 PTHREAD_STACK_MIN,16384(字节)。
2.管理线程
"一对一"模型的好处之一是线程的调度由核心完成了,而其他诸如线程取消、线程间的同步等工作,都是在核外线程库中完成的。在LinuxThreads中,专门为每一个进程构造了一个管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create()创建一个线程的时候就会创建(__clone())并启动管理线程。
在一个进程空间内,管理线程与其他线程之间通过一对"管理管道(manager_pipe[2])"来通讯,该管道在创建管理线程之前创建,在成功启动了管理线程之后,管理管道的读端和写端分别赋给两个全局变量__pthread_manager_reader和 __pthread_manager_request,之后,每个用户线程都通过__pthread_manager_request向管理线程发请求,但管理线程本身并没有直接使用__pthread_manager_reader,管道的读端(manager_pipe[0])是作为__clone()的参数之一传给管理线程的,管理线程的工作主要就是监听管道读端,并对从中取出的请求作出反应。
创建管理线程的流程如下所示:
(全局变量pthread_manager_request初值为-1)
图3创建管理线程的流程
初始化结束后,在__pthread_manager_thread中记录了轻量级进程号以及核外分配和管理的线程id, 2*PTHREAD_THREADS_MAX+1这个数值不会与任何常规用户线程id冲突。管理线程作为pthread_create()的调用者线程的子线程运行,而pthread_create()所创建的那个用户线程则是由管理线程来调用clone()创建,因此实际上是管理线程的子线程。(此处子线程的概念应该当作子进程来理解。)
__pthread_manager()就是管理线程的主循环所在,在进行一系列初始化工作后,进入while(1)循环。在循环中,线程以2秒为 timeout查询(__poll())管理管道的读端。在处理请求前,检查其父线程(也就是创建manager的主线程)是否已退出,如果已退出就退出整个进程。如果有退出的子线程需要清理,则调用pthread_reap_children()清理。
然后才是读取管道中的请求,根据请求类型执行相应操作(switch-case)。具体的请求处理,源码中比较清楚,这里就不赘述了。
3.线程栈
在LinuxThreads中,管理线程的栈和用户线程的栈是分离的,管理线程在进程堆中通过malloc()分配一个THREAD_MANAGER_STACK_SIZE字节的区域作为自己的运行栈。
用户线程的栈分配办法随着体系结构的不同而不同,主要根据两个宏定义来区分,一个是NEED_SEPARATE_REGISTER_STACK,这个属性仅在IA64平台上使用;另一个是FLOATING_STACK宏,在i386等少数平台上使用,此时用户线程栈由系统决定具体位置并提供保护。与此同时,用户还可以通过线程属性结构来指定使用用户自定义的栈。因篇幅所限,这里只能分析i386平台所使用的两种栈组织方式:FLOATING_STACK方式和用户自定义方式。
在FLOATING_STACK方式下,LinuxThreads利用mmap()从内核空间中分配8MB空间(i386系统缺省的最大栈空间大小,如果有运行限制(rlimit),则按照运行限制设置),使用mprotect()设置其中第一页为非访问区。该8M空间的功能分配如下图:
图4栈结构示意
低地址被保护的页面用来监测栈溢出。
对于用户指定的栈,在按照指针对界后,设置线程栈顶,并计算出栈底,不做保护,正确性由用户自己保证。
不论哪种组织方式,线程描述结构总是位于栈顶紧邻堆栈的位置。
4.线程id和进程id
每个LinuxThreads线程都同时具有线程id和进程id,其中进程id就是内核所维护的进程号,而线程id则由LinuxThreads分配和维护。
求助,关于linux的线程同步问题
【Linux多线程】三个经典同步问题
标签:多线程同步生产者与消费者写者与读者
目录(?)[+]
在了解了《同步与互斥的区别》之后,我们来看看几个经典的线程同步的例子。相信通过具体场景可以让我们学会分析和解决这类线程同步的问题,以便以后应用在实际的项目中。
一、生产者-消费者问题
问题描述:
一组生产者进程和一组消费者进程共享一个初始为空、大小为 n的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
分析:
关系分析:生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,它们也是同步关系。
整理思路:这里比较简单,只有生产者和消费者两个进程,且这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步的PV操作的位置。
信号量设置:信号量mutex作为互斥信号量,用于控制互斥访问缓冲池,初值为1;信号量full用于记录当前缓冲池中“满”缓冲区数,初值为 0;信号量empty用于记录当前缓冲池中“空”缓冲区数,初值为n。
代码示例:(semaphore类的封装见下文)
#include<iostream>
#include<unistd.h>// sleep
#include<pthread.h>
#include"semaphore.h"
using namespace std;
#define N 5
semaphore mutex("/", 1);//临界区互斥信号量
semaphore empty("/home", N);//记录空缓冲区数,初值为N
semaphore full("/home/songlee",0);//记录满缓冲区数,初值为0
int buffer[N];//缓冲区,大小为N
int i=0;
int j=0;
void* producer(void* arg)
{
empty.P();// empty减1
mutex.P();
buffer[i]= 10+ rand()% 90;
printf("Producer%d write Buffer[%d]:%d\n",arg,i+1,buffer[i]);
i=(i+1)% N;
mutex.V();
full.V();// full加1
}
void* consumer(void* arg)
{
full.P();// full减1
mutex.P();
printf("\033[1;31m");
printf("Consumer%d read Buffer[%d]:%d\n",arg,j+1,buffer[j]);
printf("\033[0m");
j=(j+1)% N;
mutex.V();
empty.V();// empty加1
}
int main()
{
pthread_t id[10];
//开10个生产者线程,10个消费者线程
for(int k=0; k<10;++k)
pthread_create(&id[k], NULL, producer,(void*)(k+1));
for(int k=0; k<10;++k)
pthread_create(&id[k], NULL, consumer,(void*)(k+1));
sleep(1);
return 0;
}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455561234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
编译运行输出结果:
Producer 1 write Buffer[1]: 83
Producer 2 write Buffer[2]: 26
Producer 3 write Buffer[3]: 37
Producer 5 write Buffer[4]: 35
Producer 4 write Buffer[5]: 33
Consumer 1 read Buffer[1]: 83
Producer 6 write Buffer[1]: 35
Consumer 2 read Buffer[2]: 26
Consumer 3 read Buffer[3]: 37
Consumer 4 read Buffer[4]: 35
Consumer 5 read Buffer[5]: 33
Consumer 6 read Buffer[1]: 35
Producer 7 write Buffer[2]: 56
Producer 8 write Buffer[3]: 22
Producer 10 write Buffer[4]: 79
Consumer 9 read Buffer[2]: 56
Consumer 10 read Buffer[3]: 22
Producer 9 write Buffer[5]: 11
Consumer 7 read Buffer[4]: 79
Consumer 8 read Buffer[5]: 1112345678910111213141516171819201234567891011121314151617181920
二、读者-写者问题
问题描述:
有读者和写者两组并发线程,共享一个文件,当两个或以上的读线程同时访问共享数据时不会产生副作用,但若某个写线程和其他线程(读线程或写线程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:
允许多个读者可以同时对文件执行读操作;
只允许一个写者往文件中写信息;
任一写者在完成写操作之前不允许其他读者或写者工作;
写者执行写操作前,应让已有的读者和写者全部退出。
分析:
关系分析:由题目分析可知,读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。
整理思路:写者是比较简单的,它与任何线程互斥,用互斥信号量的 PV操作即可解决。读者的问题比较复杂,它必须实现与写者的互斥,多个读者还可以同时读。所以,在这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者的时候写者是无法写文件的,此时读者会一直占用文件,当没有读者的时候写者才可以写文件。同时,不同的读者对计数器的访问也应该是互斥的。
信号量设置:首先设置一个计数器count,用来记录当前的读者数量,初值为0;设置互斥信号量mutex,用于保护更新 count变量时的互斥;设置互斥信号量rw用于保证读者和写者的互斥访问。
代码示例:
#include<iostream>
#include<unistd.h>// sleep
#include<pthread.h>
#include"semaphore.h"
using namespace std;
int count= 0;//记录当前的读者数量
semaphore mutex("/",1);//用于保护更新count变量时的互斥
semaphore rw("/home",1);//用于保证读者和写者的互斥
void* writer(void* arg)
{
rw.P();//互斥访问共享文件
printf(" Writer%d start writing...\n", arg);
sleep(1);
printf(" Writer%d finish writing...\n", arg);
rw.V();//释放共享文件
}
void* reader(void* arg)
{
mutex.P();//互斥访问count变量
if(count== 0)//当第一个读线程读文件时
rw.P();//阻止写线程写
++count;//读者计数器加1
mutex.V();//释放count变量
printf("Reader%d start reading...\n", arg);
sleep(1);
printf("Reader%d finish reading...\n", arg);
mutex.P();//互斥访问count变量
--count;//读者计数器减1
if(count== 0)//当最后一个读线程读完文件
rw.V();//允许写线程写
mutex.V();//释放count变量
}
int main()
{
pthread_t id[8];//开6个读线程,2个写线程
pthread_create(&id[0], NULL, reader,(void*)1);
pthread_create(&id[1], NULL, reader,(void*)2);
pthread_create(&id[2], NULL, writer,(void*)1);
pthread_create(&id[3], NULL, writer,(void*)2);
pthread_create(&id[4], NULL, reader,(void*)3);
pthread_create(&id[5], NULL,reader,(void*)4);
sleep(2);
pthread_create(&id[6], NULL, reader,(void*)5);
pthread_create(&id[7], NULL,reader,(void*)6);
sleep(4);
return 0;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
编译运行的结果如下:
Reader 2 start reading...
Reader 1 start reading...
Reader 3 start reading...
Reader 4 start reading...
Reader 1 finish reading...
Reader 2 finish reading...
Reader 3 finish reading...
Reader 4 finish reading...
Writer 1 start writing...
Writer 1 finish writing...
Writer 2 start writing...
Writer 2 finish writing...
Reader 5 start reading...
Reader 6 start reading...
Reader 5 finish reading...
Reader 6 finish reading...1234567891011121314151612345678910111213141516
三、哲学家进餐问题
问题描述:
一张圆桌上坐着 5名哲学家,桌子上每两个哲学家之间摆了一根筷子,桌子的中间是一碗米饭,如图所示:
哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿的时候,才试图拿起左、右两根筷子(一根一根拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
分析:
关系分析:5名哲学家与左右邻居对其中间筷子的访问是互斥关系。
整理思路:显然这里有 5个线程,那么要如何让一个哲学家拿到左右两个筷子而不造成死锁或饥饿现象?解决方法有两个,一个是让他们同时拿两个筷子;二是对每个哲学家的动作制定规则,避免饥饿或死锁现象的发生。
信号量设置:定义互斥信号量数组chopstick[5]={1,1,1,1,1}用于对 5根筷子的互斥访问。
示例代码: