服务器编程入门(九)多线程编程

服务器编程入门(9)多线程编程
问题聚焦:
    在简单地介绍线程的基本知识之后,主要讨论三个方面的内容:
    1 创建线程和结束线程;
    2 读取和设置线程属性;
    3 线程同步方式:POSIX信号量,互斥锁和条件变量。



Linux线程概述
线程模型
程序中完成一个独立任务的完整执行序列,即一个可调度的实体。
分为内核线程和用户线程
当进程的一个内核线程获得CPU的使用权时,它就加载并运行一个用户线程,可见,内核线程相当于用户线程运行的“容器”。
一个进程可以拥有M个内核线程和N个用户线程, M<=N。

线程实现
完全在用户空间实现线程的特点:
  • 创建和调度线程都无须内核的干预,因此速度相当快。
  • 不占用额外的内核资源,很多线程不会对系统性能造成明显影响。
  • (缺点)一个进程的多个线程无法运行在不同的CPU上

完全在内核空间实现线程的优缺点则和上面的实现相反,优缺点也互换。
双层调度模式是前两种实现模式的混合体
  • 内核调度M个内核线程,线程库调度N个用户线程
  • 不会过度消耗内核资源,又可以充分利用多处理器的优势



创建线程和结束线程
基础API

创建pthread_create
定义:
#include <pthread.h>
int pthread_create ( pthread_t* thread, const pthread_attr_t* attr,
                                  void * (*start_routine)(void*) , void* arg);
参数说明:
thread:新线程的标识符, 实际是一个整型,并且,Linux上几乎所有的资源标识符都是一个整型数,比如socket。
attr:用于设置新线程的属性,传递NULL表示使用默认线程属性
start_routing和arg:分别指定新线程将运行的函数及其参数。
返回:
成功时返回0,失败时返回错误码

退出线程pthread_exit
定义:
#include <pthread.h>
void pthread_exit ( void* retval );
pthread_exit函数通过retval参数向线程的回收者传递其退出信息,它执行完之后不会返回到调用者,而且永远不会失败。

回收其他线程pthread_join
定义:
#include <pthread.h>
int pthread_join( pthread_t thread, void** retval );
参数说明:
thread:目标线程的标识符
retval:目标线程返回的退出信息
效果:该函数会一直阻塞,直到被回收的线程结束为止
返回:成功时返回0,失败时返回错误码。

取消线程pthread_cancel
定义:
#include <pthread.h>
int pthread_cancel ( pthread_t thread );
接收到取消请求的目标线程可以决定是否允许被取消以及如何取消。
#include <pthread.h>
int pthread_setcancelstate( int state, int *oldstate );
int pthread_setcanceltype ( int type, int *oldtype );
这两个函数的第一个参数分别用于设置线程的取消状态和取消类型
第二个参数分别记录线程原来的取消状态和取消类型。(具体有那些装填和类型,用到时我们再自行百度)



线程属性
pthread_attr_t结构体,完整的线程属性。
定义:
#include <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类型的变量,以方便我们获取和设置线程属性。



接下来我们讨论3种专门用于线程同步的机制:POSIX信号量,互斥量和条件变量

POSIX信号量
常用API
#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 );              // 以原子操作的方式将信号量减1,如果信号量的值为0,则阻塞,直到该值不为0.
int sem_trywait ( sem_t* sem );         // sem_wait的非阻塞版本
int sem_post ( sem_t* sem );             // 以原子操作的方式将信号量的值加1
上面这些函数成功时返回0,失败时返回-1.


互斥锁
作用:用于保护关键代码段,以确保其独占式访问。
类似上一篇讲到的进程的信号量机制的PV操作.请参考 服务器编程入门(8)多进程编程
基础API:
定义:
#include <pthread.h>
int pthread_mutex_init ( pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr );
int pthread_mutex_destroy ( pthread_mutex_t* mutex );
int pthread_mutex_lock ( pthread_mutex_t* mutex );
int pthread_mutex_trylock ( pthread_mutex_t* mutex );
int pthread_mutex_unlock ( pthread_mutex_t* mutex );
互斥锁属性:
pthread_mutexattr_t结构体中定义了一套完整的互斥锁属性
这里我们列出其中一些主要的函数
#include <pthread.h>
/* 初始化互斥锁属性对象 */
int pthread_mutexattr_init ( pthread_mutexattr_t* attr );
/* 销毁互斥锁属性对象 */
int pthread_mutexattr_destroy ( pthread_mutexattr_t* attr );
/* 获取和设置互斥锁的pshared属性 */
int pthread_mutexattr_getpshared ( const pthread_mutexattr_t* attr, int * pshared );
int pthread_mutexattr_setpshared ( pthread_mutexattr_t* attr, int pthread );
/* 获取和设置互斥锁的type属性 */
int pthread_mutexattr_gettype ( const pthread_mutexattr_t* atr, int * type );
int pthread_mutexattr_settype ( pthread_mutexattr_t* attr, int type );
这里提到了两种常用属性:pshared 和type
pshared指定是否允许跨进程共享互斥锁(2种)
type指定互斥锁的类型(4种)

死锁:
结果:导致一个或多个线程被挂起而无法继续执行
原因:
对一个已经加锁的普通锁再次枷锁,将导致死锁,这种情况可能出现在不够仔细的递归函数中
如果两个线程按照不同的数学怒申请两个互斥锁,也容易产生死锁
举例:
这段代码或许总能成功的运行(不加入sleep函数刻意导致死锁),但是会为程序留下一个潜在的bug。
#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;
}

条件变量
互斥锁的作用:用于同步线程对共享数据的访问
条件变量:用于在线程之间同步同享数据的值。
作用:提供了一种线程间通知的机制,当某个共享数据打到某个值的时候,唤醒等待这个共享数据的线程
相关函数:
#include <pthread.h>
int pthread_cond_init (pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
int pthread_cond_destroy ( pthread_cond_t* cond );
int pthread_cond_broadcast ( pthread_cond_t* cond );        //以广播的形式唤醒一个等待目标条件变量的线程
int pthread_cond_signal ( pthread_cond_t* cond );              //唤醒一个等待目标条件变量的线程
int pthread_cond_wait ( pthread_cond_t* cond,  pthread_mutex_t* mutex );    // 等待目标条件变量,mutex参数保证对条件变量及其等待队列的操作原子性。


对三种同步机制的封装:
这是原书的代码,我也没有加注释,上面的api都了解了,这里也不会有什么问题。
#ifndef LOCKER_H
#define LOCKER_H

#include <exception>
#include <pthread.h>
#include <semaphore.h>

class sem
{
public:
    sem()
    {
        if( sem_init( &m_sem, 0, 0 ) != 0 )
        {
            throw std::exception();
        }
    }
    ~sem()
    {
        sem_destroy( &m_sem );
    }
    bool wait()
    {
        return sem_wait( &m_sem ) == 0;
    }
    bool post()
    {
        return sem_post( &m_sem ) == 0;
    }

private:
    sem_t m_sem;
};

class locker
{
public:
    locker()
    {
        if( pthread_mutex_init( &m_mutex, NULL ) != 0 )
        {
            throw std::exception();
        }
    }
    ~locker()
    {
        pthread_mutex_destroy( &m_mutex );
    }
    bool lock()
    {
        return pthread_mutex_lock( &m_mutex ) == 0;
    }
    bool unlock()
    {
        return pthread_mutex_unlock( &m_mutex ) == 0;
    }

private:
    pthread_mutex_t m_mutex;
};

class cond
{
public:
    cond()
    {
        if( pthread_mutex_init( &m_mutex, NULL ) != 0 )
        {
            throw std::exception();
        }
        if ( pthread_cond_init( &m_cond, NULL ) != 0 )
        {
            pthread_mutex_destroy( &m_mutex );
            throw std::exception();
        }
    }
    ~cond()
    {
        pthread_mutex_destroy( &m_mutex );
        pthread_cond_destroy( &m_cond );
    }
    bool wait()
    {
        int ret = 0;
        pthread_mutex_lock( &m_mutex );
        ret = pthread_cond_wait( &m_cond, &m_mutex );
        pthread_mutex_unlock( &m_mutex );
        return ret == 0;
    }
    bool signal()
    {
        return pthread_cond_signal( &m_cond ) == 0;
    }

private:
    pthread_mutex_t m_mutex;
    pthread_cond_t m_cond;
};

#endif



小结:
    这篇看似内容很多,其实大都是走马观花,了解一下整个框架和常用的API,起到一个入门和索引的作用,真正用到的时候,知道从哪里入手。
    这一本书的大概框架就被我们浏览完了(排除了我不太感兴趣的几个章节,如果需要的话,我们会回头再来研究的。)
    关于服务器编程,目前来说只能算是兴趣了解一下,并没有太深入,这段时间事情比较多,打算看点别的方面的资料,后面的进度上可能会慢一点了。




参考资料:
《Linux高性能服务器编程》