多线程编程


Linux主题概述


线程模型


线程是程序中完毕一个独立任务的完整执行序列。即一个可调度的实体。

依据执行环境和调度者的身份。线程可分为内核线程和用户线程。内核线程,在有的系统上也称为LWP(Light Weigth Process。轻量级进程)。执行在内核空间,由内核来调度;用户线程执行在用户空间,由线程库来调度。当进程的一个内核线程获得CPU的使用权时。它就载入并执行一个用户线程。可见,内核线程相当于用于线程执行的容器。

一个进程能够拥有M个内核线程和N个用户线程,当中M≤N。而且在一个系统的全部进程中,M和N的比值都是固定的。依照M:N的取值。线程的实现方式可分为三种模式:全然在用户空间实现、全然由内和调度和双层调度。

全然在用户空间实现的线程无需内核的支持,内核甚至根本不知道这些线程的存在。

线程库负责管理全部运行线程。比方线程的优先级、时间片等。

线程库利用longjmp来切换线程的运行。使它们看起来像是并发运行的。但实际上内核仍然是把整个进程作为最小单位来调度的。换句话说,一个进程的全部运行线程共享该进程的时间片,它们对外表现出同样的优先级。

因此,对这样的实现方式而言,N=1,即M个用户空间线程对于1个内核线程,而该内核线程实际上就是进程本身。全然在用户空间实现的线程的长处是:创建和调度线程都无须内核的干预,因此速度相当快。而且由于它不占用额外的内核资源,全部即使一个进程创建了非常多线程,也不会对系统性能造成明显的影响。

其缺点是,对于多处理器系统,一个进程的多个线程无法运行在不同的CPU上。由于内核是依照其最小调度单位来分配CPU的。此外,线程的优先级仅仅对同一个进程中的线程有效。比較不同进程中的线程的优先级没有意义。

全然由内核调度的模式将创建、调度线程的任务都交给了内核,执行在用户空间的线程无需执行管理任务,这与全然在用户空间实现的线程恰恰相反。全然由内核调度的这样的线程实现方式满足M:N=1:1,即1个用户空间线程被映射为1个内核线程。

双层调度模式是前两种实现模式的混合体:内核调度M个内核线程。线程库调度N个用户线程。这样的线程实现方式结合了前两种方式的长处:不但不会消耗过多的内核资源,并且线程切换速度也较快,同一时候她能够充分利用多处理器的优势。

创建线程和结束线程


pthread_create



#include <pthread.h>

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

thread參数是新县城的标识符。兴许pthread_*函数通过它来应用新线程。其类型pthread_t定义例如以下:

#include <bits/pthreadtypes.h>

typedef unsigned long int pthread_t

arg參数用于设置新线程的属性。

给它传递NULL表示使用默认线程属性。

线程拥有众多属性,我们将在后面讨论。start_routine和arg參数分别指定新线程将执行的函数及其參数。pthread_create成功时返回0。失败是返回错误码。

pthread_exit



线程一旦被创建好,内核就能够调度内核线程来运行start_routine函数指针所指向的 函数了。

线程函数在结束时最好调用例如以下函数,以确保安全、干净退出。

#include <pthread.h>

void pthread_exit(void *retval);

pthread_exit函数通过retval參数向线程的回收者传递其退出信息。它运行完之后不会返回到调用者。并且永远不会失败。

pthread_join



一个进程中的全部线程都能够调用pthread_join函数来回收其它线程,即等待其它线程结束,这类似于回收进程的wait和waitpid系统调用。

pthread_join的定义例如以下:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

thread參数是目标线程的标识符,retval參数则是目标线程返回的退出信息。该函数会一直堵塞,知道被回收的线程结束为止。该函数成功时返回0,失败时返回错误码。可能的错误码例如以下表:

错误码

描写叙述

EDEADLK

可能引起死锁。

比方两个线程互相对对方调用pthread_join,或者线程对自身调用pthread_join

EINVAL

目标线程是不可回收的,或者已经有其它线程在回收该目标线程

ESRCH

目标线程不存在

pthread_cancle



有时候我们希望终止一个线程。即取消线程,它是通过例如以下函数实现的:

#include <pthread.h>

int pthread_cancel(pthread_t thread);

thread參数是目标线程的标识符。该函数成功时返回0。失败时返回错误码。只是,接收到取消请求的目标线程能够决定是否同意被取消以及怎样取消,这分别由例如以下两个函数完毕。

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);

int pthread_setcanceltype(int type, int *oldtype);

这两个函数的第一个參数分别用于设置线程的取消状态(是否同意取消),和取消类型(怎样取消)。第二个參数则分别 线程原来的取消状态和取消类型。state參数有两个可选值:

PTHREAD_CANCEL_ENABLE,同意线程被取消。它是线程被创建的默认取消状态。

PTHREAD_CANCEL_DISABLE。禁止线程被取消。这样的情况下。假设一个线程收到取消请求。则它会将请求挂起,直到该线程同意被取消。

type參数也有两个可选值:

PTHREAD_CANCEL_ASYNCHRONOUS,线程随时能够被取消。它将使得接收到取消请求的目标线程马上採取行动。

PTHREAD_CANCEL_DEFERROR,同意目标线程推迟行动,直到它调用了所谓的取消点函数。

这两个函数成功时返回0,失败时返回错误码。

线程属性


pthread_attr_t结构体定义了一套完整的线程属性。例如以下所看到的:

#inlcude <bits/pthreadtypes.h>

#define _SIZEOF_PTHREAD_ATTR_T 36

typedef union

{

char __size[__SIZEOF_PTHREAD_ATTR_T];

long int __align;

} pthread_attr_t;

各种线程属性武安不包括在一个字符数组中。线程库定义了一些列函数来操作pthread_attr_t类型的变量,以方便我们获取和设置线程属性。

我们能够用pthread_attr_t结构改动线程默认属性,并把这些属性与创建的线程联系起来。能够使用pthread_attr_init函数初始化pthreaad_attr_t结构(或者叫初始化线程属性对象)。调用pthread_attr_init以后,pthread_attr_t结构所包括的内容就是操作系统实现支持的线程全部属性的默认值。假设要改动当中个别属性的值,须要调用其它的函数。pthread_attr_destroy能够去除对pthread_attr_t结构的初始化(销毁线程属性对象)。

#include<pthread.h>

intpthread_attr_init(pthread_attr_t *attr);

intpthread_attr_destroy(pthread_attr_t *attr);

POSIX.1定义的线程属性主要有detachstate(线程的分离状态属性),guardsize(线程栈末尾的警戒缓冲区大小),stackaddr(线程栈最低地址),stacksize(线程栈的大小(字节数))。

假设对现有的某个线程的终止状态不感兴趣,能够使用pthread_detach函数让操作系统在线程退出时回收所占用的资源。假设在创建线程时就知道不须要了解线程的终止状态。则能够改动pthread_attr_t结果中的detachstate线程属性,让线程以分离状态启动。

能够使用pthread_attr_setdetachstate把线程属性detachstate设置为以下的合法值之中的一个:设置PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者以PTHREAD_CREATE_JOINABLE,正常启动线程,引用程序能够获取线程的终止状态。

能够调用pthread_attr_getdetachstate函数获取当前detachstate线程属性。第二个參数所指向的整数或许被设置为PTHREAD_CREATE_DETACHED。也可能被设置为PTHREAD_CREATE_JOINABLE。

#include<pthread.h>

intpthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

int pthread_attr_getdetachstate(pthread_attr_t*attr, int *detachstate)

函数pthread_attr_getstack和pthread_attr_setstack能够对线程栈属性进行查询和改动。

#include<pthread.h>

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);

这两个函数能够用于管理stackaddr线程属性和stacksize线程属性。应用程序也能够通过pthread_attr_setstacksize和pthread_attr_getstacksize函数读取或设置线程属性stacksize。

#include<pthread.h>

intpthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

intpthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存大小。

这个属性默认设置为PAGESIZE个字节。能够把guardsize线程属性设置为0。从而不同意属性的这样的特征行为发生:在这样的情况下。不会提供警戒缓冲区。相同的,如果对线程属性stackaddr做了改动,系统就会如果我们会自己管理栈,并使警戒缓冲区机制无效,等同于guardsize线程属性设为0。

#include<pthread.h>

intpthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);

intpthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);

假设guardsize线程属性被改动了,操作系统可能把它取为页大小的整数倍。

假设线程的栈指针溢出到警戒区,应用程序就能够通过信号接收到出错信息。

POSIX信号量


线程同步的机制以下讲3种:信号量、相互排斥量和条件变量。

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

int sem_destroy(sem_t *sem);

int sem_wait(sem_t *sem);

int sem_trywait(sem_t *sem);

int sem_post(sem_t *sem);

这些函数的第一个參数sem指向被操作的信号量。

sem_int用于初始化一个未命名的信号量。pshared參数指定信号量的类型。假设pshared參考指定信号量的类型。假设其值为0。就表示这个信号量是当前进程的局部信号量。否则该信号量就能够在多个进程之间共享。

value參数指定信号量的初始值。此外,初始化一个已经被初始化的信号量将导致不可预期的结果。

sem_destroy函数用于销毁信号量,以释放期占用的内核资源。

假设销毁一个正在被其它线程等待的信号量,则将导致不可预期的后果。

sem_wait函数以原子操作的方式将信号量减1。

假设信号量的值为0。则sem_wait将被堵塞。直到这个信号量具有非0值。

sem_trywait与sem_wait函数相似。只是它始终马上返回,而不论被操作的信号是否具有非0值,相当于sem_wait的非堵塞版本号。

当信号量的值非0时,sem_trywait对信号量运行减1操作。当信号量的值非0时,sem_trywait对信号量运行减1操作。

当信号量的值为0时,它将返回-1并设置errno为EAGAIN。

sem_post函数以原子操作的方式将信号量的值加1.当信号量的值大于0时,其它正在调用sem_wait等待信号量的线程将被唤醒。

上面这些函数成功时返回0,失败是返回-1并设置errno。

相互排斥锁


相互排斥锁基础API


POSIX相互排斥锁的相关函数主要有例如以下5个:

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

int pthread_mutex_init(pthread_mutex_t *restrict mutex,

const pthread_mutexattr_t *restrict attr);

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

这些函数的第一个參数mutex指向操作的目标相互排斥锁,相互排斥锁的类型是pthread_mutex_t结构体。

pthread_mutex_init函数用于初始化相互排斥锁。

mutexattr參数指定相互排斥锁的属性。假设将它设置为NULL。则表示使用默认属性。除了这个函数外,我们还能够用例如以下方式初始化一个相互排斥锁:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

宏PTHREAD_MUTEX_INITIALIZER实际上仅仅是把相互排斥锁的各个字段都初始化为0。

pthread_mutex_destroy函数用于小胡相互排斥锁,以释放期占用的内核资源。

销毁一个已经加锁的相互排斥锁将导致不可预期的后果。

pthread_mutex_lock函数以原子操作的方式给一个相互排斥锁加锁。

假设目标相互排斥锁已经被锁上,则pthread_mutex_lock调用将堵塞。直到该相互排斥锁的占有者将其解锁。

pthread_mutex_trylock与pthread_mutex_lock函数类似。只是它始终马上返回。而不论被操作的相互排斥锁是否已经加锁,相当于pthread_mutex_lock的非堵塞版本号。

当目标相互排斥锁未被加锁时,pthread_mutex_trylock对相互排斥锁运行加锁操作。

当相互排斥锁已经被加锁时,pthread_mutex_trylock将返回错误码EBUSY。

须要注意的是,这里讨论的pthread_mutex_lock和pthread_mutex_trylock的行为是针对普通锁而言的。

pthread_mutex_unlock函数以院子操作的方式给一个相互排斥锁解锁。假设此时有其它线程正在等待这个相互排斥锁。则这些线程中的某一个将获得它。

上面这些函数成功时返回0。失败时返回错误码。

相互排斥锁属性


pthread_mutexattr_t结构体定义了一套完整的相互排斥锁属性。

线程库提供了一系列函数来操作pthread_mutexattr_t类型变量,以方便我们获取和设置相互排斥锁属性。这里我们列出当中一些基本的函数:

#include <pthread.h>

int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

int pthread_mutexattr_init(pthread_mutexattr_t *attr);

int pthread_mutexattr_getpshared(const pthread_mutexattr_t *

restrict attr, int *restrict pshared);

int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);

int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

这里仅仅讨论相互排斥锁的两种经常使用属性:pshared和type。

相互排斥锁属性pshared指定是否同意跨进程共享相互排斥锁。其可选值有两个:

PTHREAD_PROCESS_SHARED。相互排斥锁能够被跨进程共享。

PTHREAD_PROCESS_PRIVATE。

相互排斥锁仅仅能被和锁的初始化线程隶属于同一个进程的线程共享。

相互排斥锁属性type指定相互排斥锁的类型。Linux支持例如以下4种类型的相互排斥锁:

PTHREAD_MUTEX_NORMAL。普通锁。

这是相互排斥锁默认的类型。

当一个线程对一个普通锁加锁以后。其余请求该所的线程将形成一个等待队列,并在该所解锁后按优先级获得它。这样的锁类型保证了资源分配的公平性。但这样的锁也非常easy引发问题:一个线程假设对一个已经加锁的普通锁再次加锁,将引发死锁。对一个已经被其它线程加锁的普通锁解锁,或者对一个已经解锁的普通锁解锁将导致不可预期的后果。

PTHREAD_MUTEX_ERRORCHECK,检错锁。

一个线程假设对一个已经加锁的检错锁再次加锁,则加锁操作返回EDEADLK。对一个已经被其让他线程加锁的检错锁解锁,或者对一个已经解锁的检错锁再次解锁,则检错锁返回EPERM。

PTHREAM_MUTEX_RECURSIVE。嵌套锁。

这样的锁同意一个线程在释放锁之前对他加锁而不发生死锁。只是其它线程假设要获得这个锁,则当前锁的拥有者必须运行对应次数的解锁操作。

对一个已经被其它线程枷锁的嵌套锁解锁。或者对一个已经解锁的嵌套锁再次解锁,则解锁操作返回EPERM。

PTHREAD_MUTEX_DEFAULT。默认锁。

一个线程假设对一个已经加锁的默认锁再次加锁。或者对一个已经被其它线程加锁的默认锁解锁,或者对一个已经解锁的默认锁再次解锁,将导致不可预期的后果。

死锁举例


使用相互排斥锁的一个噩耗是死锁。

死锁使得一个或多个线程被挂起而无法继续运行,并且这样的情况还不easy被发现。

在一个线程中对还有一个已经加锁的普通锁再次加锁将导致死锁,这样的情况可能出如今设计的不够细致的递归函数中。

另外,假设两个线程依照不同的顺序来申请两个相互排斥锁,也easy产生死锁。

例如以下所看到的便是按不同顺序訪问相互排斥锁导致死锁的实例:

#include <pthread.h>
#include <unistd.h>
#include <stdio.h> int a = 0;
int b = 0;
pthread_mutex_t mutex_a;
pthread_mutex_t mutex_b; void* another( void* arg )
{
pthread_mutex_lock( &mutex_b );
printf( "in child thread, got mutex b, waiting for mutex a\n" );
sleep( 5 );
++b;
pthread_mutex_lock( &mutex_a );
b += a++;
pthread_mutex_unlock( &mutex_a );
pthread_mutex_unlock( &mutex_b );
pthread_exit( NULL );
} int main()
{
pthread_t id; pthread_mutex_init( &mutex_a, NULL );
pthread_mutex_init( &mutex_b, NULL );
pthread_create( &id, NULL, another, NULL ); pthread_mutex_lock( &mutex_a );
printf( "in parent thread, got mutex a, waiting for mutex b\n" );
sleep( 5 );
++a;
pthread_mutex_lock( &mutex_b );
a += b++;
pthread_mutex_unlock( &mutex_b );
pthread_mutex_unlock( &mutex_a ); pthread_join( id, NULL );
pthread_mutex_destroy( &mutex_a );
pthread_mutex_destroy( &mutex_b );
return 0;
}

代码中增加sleep函数来模拟连续调用pthread_mutex_lock之间的时间差,以确保代码中的两个线程各自占有一个相互排斥锁,然后等待另外一个相互排斥锁。这样,两个线程就僵持住了,谁都不能继续往下执行,从而形成死锁。假设代码中不增加sleep函数,则这段代码也许总能成功执行,从而为程序留下一了个潜在的BUG。

版权声明:本文博主原创文章,博客,未经同意不得转载。

Linux高性能server规划——多线程编程(在)的更多相关文章

  1. Linux高性能server规划——多进程编程

    多进程编程 多进程编程包含例如以下内容: 复制进程影映像的fork系统调用和替换进程映像的exec系列系统调用. 僵尸进程以及怎样避免僵尸进程 进程间通信(Inter-Process Communic ...

  2. Linux高性能server规划——处理池和线程池

    进程池和线程池 池的概念 由于server的硬件资源"充裕".那么提高server性能的一个非常直接的方法就是以空间换时间.即"浪费"server的硬件资源.以 ...

  3. linux下C语言多线程编程实例

    用一个实例.来学习linux下C语言多线程编程实例. 代码目的:通过创建两个线程来实现对一个数的递加.代码: //包含的头文件 #include <pthread.h> #include ...

  4. Linux高性能server编程——多线程编程(下)

    多线程编程 条件变量 假设说相互排斥锁是用于同步线程对共享数据的訪问的话.那么条件变量则是用于线程之间同步共享数据的值. 条件变量提供了一种线程间的通信机制:当某个共享数据达到某个值得时候,唤醒等待这 ...

  5. linux下C++的多线程编程

    1. 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程(proces ...

  6. Linux高性能server编程——Linux网络基础API及应用

     Linux网络编程基础API 具体介绍了socket地址意义极其API,在介绍数据读写API部分引入一个有关带外数据发送和接收的程序,最后还介绍了其它一些辅助API. socket地址API 主 ...

  7. Linux高性能server编程——信号及应用

     信号 信号是由用户.系统或者进程发送给目标进程的信息.以通知目标进程某个状态的改变或系统异常. Linux信号可由例如以下条件产生: 对于前台进程.用户能够通过输入特殊的终端字符来给它发送信号. ...

  8. Linux高性能server编程——I/O复用

     IO复用 I/O复用使得程序能同一时候监听多个文件描写叙述符.通常网络程序在下列情况下须要使用I/O复用技术: client程序要同一时候处理多个socket client程序要同一时候处理用户 ...

  9. Linux高性能server编程——定时器

    版权声明:本文为博主原创文章.未经博主允许不得转载. https://blog.csdn.net/walkerkalr/article/details/36869913  定时器 服务器程序通常管 ...

随机推荐

  1. ActiveReports 9 新功能:创新的设计分层报告

     在最新的ActiveReports 9报表控件添加了几个新功能,为了帮助您创建一个漂亮的外观在较短的时间内.强大的报表系统.本文重点讨论创新的分层设计报告,分组报告内容管理和设计,于实现报表套打 ...

  2. LA 3027 Corporative Network 并查集记录点到根的距离

    Corporative Network Time Limit: 3000MS   Memory Limit: Unknown   64bit IO Format: %lld & %llu [S ...

  3. SE 2014年5月5日

    如图配置 某企业网络规划图(三台交换设备/三台路由设备) 接入层 SW1 连接终端用户 汇聚层 SW2 SW3 核心层 R1 R2 R5 1. 如图 SW1 SW2 SW3 物理链路两两相连接,网络中 ...

  4. SWT中在treeview中显示图片

    package com.repositoryclient.treeview; import org.eclipse.jface.resource.ImageDescriptor; import org ...

  5. Cordova CLI源码分析(一)——简介

    本系列文章分析基于node.js的命令行工具Cordova CLI,所以如果对node.js基础不是很了解,建议参考http://nodejs.gamesys.net/node-js提供的基础教程 文 ...

  6. 黄聪:Microsoft Enterprise Library 5.0 系列教程(十) Configuration Application Block

    原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(十) Configuration Application Block 到目前为止,我们使用的模块都是在同一个配置 ...

  7. Android Wear 数据类型和接口的发送和同步数据概述

    Android Wear数据层API,这是google play service部分,通信信道,以你的手持设备和耐磨应用. Api它包含一系列数据对象,可以让系统通过监控和通知行app重要的事件数据层 ...

  8. ASP.NET Core MVC Hello World

    ASP.NET Core 现在ASP.NET Core还在不断成长.更新中,说不定到了明天又换了个模样,就如同一个小孩,从蹒跚学步,到奔向未来. 所以我们可以相应的去理解更新中所发生的变化,包容它.呵 ...

  9. Custom Media Player in WPF (Part 1)

    First of all I would like to welcome everyone to my new blog and wish you all a happy new year… Thro ...

  10. BI事实上的和维表定义

    一个典型的例子是,逻辑业务相比立方体,产品尺寸.时间维度.位置尺寸,分别作为不同的轴.轴的交点是一个详细的事实.这一事实表是多维度的交叉点的一个表.维表是事实的分析的一种形式. 首先介绍下数据库结构中 ...