C++多核高级编程 - 05 多线程

来源:互联网 发布:任炎的陀螺淘宝购买 编辑:程序博客网 时间:2024/06/08 15:48

一,什么是线程

线程是进程中可执行代码流的序列,它被操作系统调用,并在处理器或内核上运行。所有进程都有一个主线程,主线程是进程的控制流或执行路线。线程分为用户线程和系统线程,线程在创建,维护和管理方面给系统带来的负担要轻得多。线程用于执行系统中的并发任务,可以简化系统中固有的并发的程序的结构。

用户级线程和内核级线程

线程有三种实现模型: 用户级或应用程序级,内核级,用户级和内核级混合线程。

他们之间主要的区别在于他们的模式以及要指派给处理器的线程的能力。

用户模式下,线程驻留在用户空间,是执行程序或连接库中的指令,由库调度器进行调度。

内核模式下,线程驻留在系统空间,可以进行系统调用,由操作系统调度器调度。

用户线程在运行时,任意给定时刻,每个进程只有一个线程在运行,而且只有一个处理器内核被分配给该进程。运行时调度库从进程的多个线程中选择一个,然后该线程和进程允许的一个内核线程关联起来,用户级线程是一种多对一的线程映射。


user_thread

内核级线程驻留在内核空间,它们是内核对象,由操作系统调度器管理。有了内核线程,每个用户线程被映射或绑定到一个内核线程。 用户线程在其生命期内都会绑定到该内核线程。一旦用户线程终止,两线程都将离开系统。从内核线程到用户线程是一种一对一映射。


sys_thread


混合线程是用户线程和系统线程的交叉,使得运行库和操作系统都可以管理线程。在这种实现中进程有自己的内核线程池。可运行的用户线程由运行时库分派并标记为准备好执行的可用线程。操作系统选择用户线程并将它映射到线程池中可用的内核线程。


hy_thread


线程的上下文

操作系统管理很多进程的执行。它们来着不同的程序或系统。当一个进程从内核中移出,另一个进程成为活动的,这些进程之间便发生了上下文切换。操作系统必须记录重启进程和启动新进程使之活动所需要的所有信息。线程也有相同的处理方式。上下文保存的内容。

上下文内容进    程线    程指向可执行文件的指针X 栈XX内存(数据段和栈)X 状态XX优先级XX程序 IO 的状态X 授予权限X 调度信息X 审计信息X 有关资源的信息
-        文件描述符
-        读/写 指针X 有关事件和信号的信息X 寄存器组
-        栈指针
-        指令计数器
-        诸如此类XX


二, 线程和进程的比较

线程和进程都能提供并发的程序执行,在决定使用进程或线程时可以从上下文切换开销,吞吐量,实体间通信,程序简化等方面进行考虑。

-    上下文切换:如果只有一个处理器,线程的上下文切换的开销较小。

-    吞吐量:使用多个线程可以增加程序的吞吐量,否则只有一个线程时,线程的IO将使整个程序被阻塞。

-    实体间通信:线程与同一进程间其他线程通信时不要求特殊的通信机制,可以直接进行数据的传输。进程间则必须建立和维护它们之间的通信机制。

-    破坏进程的数据:线程可以很轻松的破坏整个进程的数据。进程有自己的地址空间,相互隔离,数据也受到保护。

-    删除整个进程:线程出错时可以导致整个进程的终止,它导致的错误往往比进程导致的错误代价更大。

-    重用性:线程依赖于进程,不能从它所属的进程分离,不可以直接被重用,进程则更加的独立。



线程与进程的类似线程与进程的差别都有ID 寄存器 状态优先级和调度策略线程共享创建它的进程的地址空间,进程有自己独立的地址空间都有用于为操作系统描述的实体属性线程能对所属进程的数据段进行直接访问,进程有着父进程的数据段的自己的副本都包含一个信息块线程可以同所属进程的线程直接进行通信,进程必须使用进程间通信才能通兄弟进程通信都与父进程共享资源线程几乎没有开销,进程的开销相当大都可以作为与父进程独立的实体创建新线程很容易,创建新进程则需复制父进程创建者可以对线程或进程进行一些控制线程可以对相同的进程或其他线程进行相当大的控制,进程只能对子进程进行控制都可以改变属性对主线程的改动(取消,优先级等改动)可能影响到进程中其他线程的行为,对父进程的改动不会影响到子进程都可以创建新的资源 都不能访问另一个进程的资源 


三, 设置线程属性

有些线程的信息可以描述线程的上下文切换,这些信息可以用于重建线程的环境。

同一进程中不同线程的主要区别是 id,定义线程状态的寄存器组,优先级和栈。


POSIX定义了线程属性对象,它封装了线程属性的一个子集,使用户可以方便的创建访问和更改。

-        竞争范围  (系统范围,进程范围)

-        栈大小

-        栈地址

-        分离状态 (是否从它的创建者中分离出来,在终止或退出时是否同其他对等线程或主线程同步)

-        优先级

-        调度策略和参数


四, 线程的结构

与进程一样,线程也有自己的上下文和属性。进程有代码段,数据段和栈段,线程同进程共享代码段和栈段。进程通常从内存的高地址开始,向下增长,线程则以下一个线程开始的地址为边界。


thread_system_constructor

线程有着和进程一样的状态和状态转换关系。主线程可以决定整个进程的状态。对于多个线程的进程,只有所有的线程都处于休眠状态整个进程才处于休眠状态。

线程在系统中有着两种竞争范围:进程竞争和系统竞争。

进程竞争:线程与相同进程内的其他线程进行竞争。

系统竞争:线程与系统范围内的进程的线程进行竞争。

举例:进程A有4个线程,进程B有2个线程,系统有8个CPU,进程A的前3个线程使用CPU 0,1 在进程范围内竞争。进程B的一个线程和进程A的一个线程公用一个CPU,在系统范围内进行竞争。


system_and_process

由于竞争,线程也有着相关的调度策略。进程的调度策略和优先级属于主线程。每个线程可以有着和主线程不同的调度策略和优先级。
调度策略分为轮询(RR)和先进先出(FIFO)两种。


五, 创建线程

下面将用一个简单的示例来说明线程的创建和管理的方式。

这种时序图可以较好的描述线程的启动和终止的时间上的关系。

thread_work


下面的程序将创建一个多线数组,并使其运行。

using namespace std;#include <iostream>#include <pthread.h>void *task(void* X){    int *Tmp;    Tmp = static_cast<int *>(X);        for(int Count = 0; Count < *Tmp; Count++)    {        cout << "Work from thread: " << Count << endl;    }    cout << "Thread complete" << endl;    return NULL;}int main(int argc, char *argv[]){    int N = 10;    pthread_t  MyThread[10];        for(int Count = 0; Count < 10; Count++)    {        pthread_create(&MyThread[Count], NULL, task, &N);    }        for(int Count = 0; Count < 10; Count++)    {        pthread_join(MyThread[Count], NULL);    }        return 0;}


六, 管理线程

这里将介绍pthread中关于线程管理的一些函数和管理方式。

(1)  创建线程

int pthread_create(pthread_t* restrict thread, const pthread_attr_t *restrict attr, void*(*start_routine), void *restrict arg)

pthread_t* restrict thread: 描述线程ID

const pthread_attr_t *restrict attr: 新线程的专有属性,如果设置为NULL则使用默认值

void*(*start_routine): 新线程将要执行的指令函数

void *restrict arg: 指令函数需要的参数

关键字restrict: 与之前的IEEE标准保持一致


(2)  结合线程

int pthread_join(pthread_t thread, void** value_ptr)

用于结合或再次结合进程中的控制流,pthread_join调用将导致调用线程执行挂起,直到目标线程终止。它类似于进程中的wait()函数,该函数由线程的创建者调用,该调用线程等待新线程终止并返回,然后再次结合到调用线程的控制流中。如果线程控制句柄可以公共访问,该函数也可以被对等线程调用。使得任何线程可以将控制流同进程中的任何其他线程结合。如果调用线程目标线程返回前被取消,则会导致目标线程成为僵死线程。

pthread_t thread: 描述线程ID

void** value_ptr: 如果目标线程返回成功,该参数保存线程的退出状态。


(3)  获得线程ID

pthead_t pthread_self(void)

线程被创建后,该函数返回线程ID。


(3)  终止线程
线程终止方式:
-     通过从它被分配的任务返回
-     显示终止自身并提供一个退出状态
-     被相同地址空间的其他线程终止

自终止:int pthread_exit(void** value_ptr)

当终止线程调用pthread_exit()后,它在 value_ptr中得到该线程的退出状态。退出状态被返回到pthread_join中。当调用这个函数时,线程所使用的资源不会被释放。


终止对等线程:int pthread_cancel(pthread_t thread)

应用程序中可能有线程监视其他线程的工作,如果发现有的线程执行不力或不再需要时,有必要使其终止。pthread_cancle的调用是取消一个对等线程的请求。这个请求可能立刻被同意,稍后被同意,甚至被忽略。目标线程可能立刻终止,稍后终止,或拒绝终止。

在取消一个对等线程的请求被同意时,会有一个取消过程,目标线程的取消状态(可取消 or 不可取消)和 取消类型(取消何时发生)决定了收到取消请求后线程继续执行的能力。

设置取消状态和取消类型:

pthread_setcancelstate(int stat, int *oldstat);

pthread_setcanceltype(int type, int* oldtype);


使用取消点

当推迟取消请求时,线程的终止会推迟到线程的函数执行后期。当取消发生时,应该是安全的,因为它不处于互斥量加锁,执行关键代码,令数据处于某种不可用状态的情况中。代码执行中,这些位置是很好的取消点。取消点是一个检查点,线程在这里检查是否有任何取消请求未决,如果有,则终止。

void pthread_testcancel(void);


另外可利用安全取消的库函数和系统调用

-     pthread_cond_wait()

-     pthread_timewait()

-     pthread_join()

当线程因调用这些函数而阻塞时,取消线程是安全的。


(4)  管理线程栈
int pthread_attr_setstacksize( pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize( pthread_attr_t *attr, size_t *stacksize);

int pthread_attr_setstackaddr( pthread_attr_t *attr, void *stackaddr);
int pthread_attr_getstackaddr( pthread_attr_t *attr, void **stackaddr);
 
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);


(5)  管理线程调度和优先级
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(pthread_attr_t *attr,int *policy);

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

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


(6)  管理线程竞争范围

int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);

int pthread_attr_getscope(const pthread_attr_t *restrict attr, int *restrict contentionscope);



七, 扩展线程接口

线程的接口类将把线程管理等功能进行封装,使得线程更易使用,功能更强,也更加灵活。

程序共4部分:

thread_object.h:定义接口类。

thread_object.cpp:接口类的实现

thread_filter.cpp:派生自接口类基类,实现简单的测试功能。

main.cpp:测试入口程序。



thread_object.h

#ifndef __THREAD_OBJECT_H#define __THREAD_OBJECT_Husing namespace std;#include <iostream>#include <pthread.h>#include <string>class thread_object{public:    thread_object();    ~thread_object();    void setPriority(int nPriority);    void setSchedPolicy(int nPolicy);    void setContentionScope(int nScope);    void setDetached(void);    void setJoinable(void);    void name(string strName);    void run(void);    void join(void);    friend void *thread(void *buff);private:    pthread_t  m_Tid;protected:    virtual void do_something(void) = 0;    pthread_attr_t       m_attr_SchedAttr;    struct sched_param   m_attr_SchedParam;    string               m_Name;    int                  m_nNewPolicy;    int                  m_nNewState;    int                  m_nNewScope;};class filter_thread: public thread_object{protected:    void do_something(void);public:    filter_thread(void);    ~filter_thread(void);};#endif

thread_object.cpp

#include "thread_object.h"thread_object::thread_object(){    pthread_attr_init(&m_attr_SchedAttr);    pthread_attr_setinheritsched(&m_attr_SchedAttr, PTHREAD_EXPLICIT_SCHED);    m_nNewState = PTHREAD_CREATE_JOINABLE;    m_nNewScope = PTHREAD_SCOPE_PROCESS;    m_nNewPolicy = SCHED_OTHER;}thread_object::~thread_object(){}void thread_object::join(void){    if (m_nNewState == PTHREAD_CREATE_JOINABLE)    {        pthread_join(m_Tid, NULL);    }}void thread_object::setPriority(int nPriority){    int nPolicy;    struct sched_param stParam;    stParam.sched_priority = nPriority;    pthread_attr_setschedparam(&m_attr_SchedAttr, &stParam);}void thread_object::setSchedPolicy(int nPolicy){    if (nPolicy == 1)    {        pthread_attr_setschedpolicy(&m_attr_SchedAttr, SCHED_RR);        pthread_attr_getschedpolicy(&m_attr_SchedAttr, &m_nNewPolicy);    }    if (nPolicy == 2)    {        pthread_attr_setschedpolicy(&m_attr_SchedAttr, SCHED_FIFO);        pthread_attr_getschedpolicy(&m_attr_SchedAttr, &m_nNewPolicy);    }}void thread_object::setContentionScope(int nScope){    if (nScope == 1)    {        pthread_attr_setscope(&m_attr_SchedAttr, PTHREAD_SCOPE_SYSTEM);        pthread_attr_getscope(&m_attr_SchedAttr, &m_nNewScope);    }    if (nScope == 2)    {        pthread_attr_setscope(&m_attr_SchedAttr, PTHREAD_SCOPE_PROCESS);        pthread_attr_getscope(&m_attr_SchedAttr, &m_nNewScope);    }}void thread_object::setDetached(void){    pthread_attr_setdetachstate(&m_attr_SchedAttr, PTHREAD_CREATE_DETACHED);    pthread_attr_getdetachstate(&m_attr_SchedAttr, &m_nNewState);}void thread_object::setJoinable(void){    pthread_attr_setdetachstate(&m_attr_SchedAttr, PTHREAD_CREATE_JOINABLE);    pthread_attr_getdetachstate(&m_attr_SchedAttr, &m_nNewState);}void thread_object::run(void){    pthread_create(&m_Tid, &m_attr_SchedAttr, thread, this);}void thread_object::name(string strName){    m_Name = strName;}void* thread(void* buff){    thread_object* pThread;    pThread = static_cast<thread_object *> (buff);    pThread->do_something();    return NULL;}

thread_filter.cpp

<pre name="code" class="cpp">#include "thread_object.h"filter_thread::filter_thread(void){    pthread_attr_init(&m_attr_SchedAttr);}filter_thread::~filter_thread(void){}void filter_thread::do_something(void){    struct sched_param stParam;    int nPolicy;    pthread_t thread_id = pthread_self();    string strSchedule;    string strState;    string strScope;    pthread_getschedparam(thread_id, &nPolicy, &stParam);    switch (m_nNewPolicy)    {    case SCHED_RR:        strSchedule.assign("RR");        break;    case SCHED_FIFO:        strSchedule.assign("FIFO");        break;    case SCHED_OTHER:        strSchedule.assign("OTHER");        break;    default:        strSchedule.assign("unknown");        break;    }    switch (m_nNewState)    {    case PTHREAD_CREATE_DETACHED:        strState.assign("DETACHED");        break;    case PTHREAD_CREATE_JOINABLE:        strState.assign("JOINABLE");        break;    default:        strState.assign("unknown");        break;    }    switch (m_nNewScope)    {    case PTHREAD_SCOPE_PROCESS:        strScope.assign("PROCESS");        break;    case PTHREAD_SCOPE_SYSTEM:        strScope.assign("SYSTEM");        break;    default:        strScope.assign("unknown");    }    cout << m_Name << " : " << thread_id << endl         << "--------------------------------------"<< endl         << " priority : " << stParam.sched_priority << endl         << " policy   : " << strSchedule            << endl         << " state    : " << strState               << endl         << " scope    : " << strScope               << endl << endl;}

main.cpp
#include "thread_object.h"#include <unistd.h>int main(int argc, char * argv[]){    filter_thread MyThread[4];    MyThread[0].name("Proteus");    MyThread[0].setSchedPolicy(2);    MyThread[0].setPriority(7);    //MyThread[0].setDetached(); // 若设置为Detached则在打印时会出现乱序现象    MyThread[1].name("Stand Along Complex");    MyThread[1].setContentionScope(1);    MyThread[1].setPriority(5);    MyThread[1].setSchedPolicy(2);    MyThread[2].name("Krell Space");    MyThread[2].setPriority(3);    MyThread[3].name("Cylon Space");    MyThread[3].setSchedPolicy(2);    MyThread[3].setPriority(2);    for (int i = 0; i < 4; i++)    {        MyThread[i].run();        MyThread[i].join();    }    return 0;}


	
				
		
原创粉丝点击