接下来看看并发服务器的第二种方法:线程
线程创建及运行
需要单独定义线程main函数,还需要请求操作系统在单独流中执行该函数
#include <pthread.h>
int pthread_create(
pthread_t * restrict thread, const pthread_attr_t * restrict attr,
void * (* start_routine)(void *), void * restrict arg);
成功返回0,失败返回其他值
thread 保存新建线程ID的变量地址值
attr 用于传递线程属性的参数,NULL为默认属性的线程
start_routine 线程的main函数,在单独执行流中执行的函数地址值
arg 通过第三个参数传递调用函数时包含传递参数信息的变量地址值
编译线程相关代码时,加-lpthread声明连接线程库,才能调用头文件pthread中声明的函数
#include <pthread.h>
int pthread_join(pthread_t thread, void ** status);
成功返回0,失败返回其他值
thread 该参数ID的线程终止后才会从该函数返回
status 保存线程的main函数返回值的指针变量地址值
根据临界区是否引起问题,线程函数可以分为两类
线程安全函数:被多个线程同时调用时也不会引发问题
非线程安全函数
线程安全函数中同样可能存在临界区,可通过措施避免问题
大多标准函数都是线程安全函数,定义非线程安全函数的同时,会提供具有相同功能的线程安全函数
声明头文件前定义_REENTRANT宏,将函数改为对应线程安全函数调用
通常在编译时加:如 gcc _D_REENTRANT mythread.c -o mthread -lpthread
线程安全函数通常加_r后缀
临界区:函数内同时运行多个线程时引起问题的多条语句构成的代码块
同时访问同一内存空间时发生的情况
需要指定访问同一内存空间的线程执行顺序的情况
互斥量的创建和销毁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);
int pthread_mutex_destroy(pthread_mutex_t * mutex);
成功返回0,失败返回其他值
mutex 互斥量的变量地址值
attr 传递互斥量属性,没有时设为NULL
可以通过宏向第二个参数传递NULL【不推荐,很难发现发生的错误】
pthread_mutex_t * mutex = PTHREAD_MUTEX_INITIALIZER
互斥量锁住和释放
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t * mutex);
//临界区
int pthread_mutex_unlock(pthread_mutex_t * mutex);
A线程进入临界区前调用pthread_mutex_lock,如果发现其他线程已进入临界区,
则pthread_mutex_lock函数不会返回,直到临界区里面的B线程调用pthread_mutex_unlock函数
退出临界区为止。B线程调用pthread_mutex_unlock函数之前,A线程一直处于阻塞状态
如果B线程退出临界区时忘记调用B线程调用pthread_mutex_unlock函数,则其他函数
为了进入临界区调用的B线程调用pthread_mutex_lock函数无法摆脱阻塞状态,这种
情况成为死锁
用互斥锁划分临界区,根据实际情况划分临界区大小
信号量
信号量创建和销毁
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
成功返回0,失败返回其他值
sem 信号量的变量地址值
pshared 传递其他值时创建可由多个进程共享的信号量,传递0时创建只允许一个进程内部使用的信号量
value 指定新创建的信号量初始值
相当于互斥量lock、unlock的函数
#include <semaphore.h>
int sem_post(sem_t * sem);
int sem_wait(sem_t * sem);
成功返回0,失败返回其他值
sem 传递保存信号量读取值的变量地址值,传递给sem_post时信号量加一,
传递给sem_wait时信号量减一
调用sem_init函数时,操作系统将创建信号量对象,此对象中记录“信号量值”整数,
该值在调用sem_post函数时加一,调用sem_wait函数时减一,但信号量值不能小于 0
sem_wait(&sem);//信号量变为0
//临界区
sem_post(&sem);//信号量变为1
调用sem_wait函数进入临界区的线程在调用sem_post函数前不允许其他线程进入临界区
信号量在0/1之间跳转,因此称为二进制信号量
线程的销毁:
线程不会自动销毁,销毁方法:
调用pthread_join函数
调用pthread_detach函数
调用pthread_join函数时,不仅会等待线程终止,还会引导线程销毁
线程终止前,调用该函数的线程将进入阻塞状态。
#include <pthread.h>
int pthread_detach(pthread_t thread);
成功返回0,失败返回其他值
thread 终止的同时需要销毁的线程ID
调用此函数不会引起线程终止或进入阻塞状态,可用该函数引导销毁线程创建的内存空间
调用该函数后不能 在针对相应线程调用pthread_join函数
有其他方法可以在创建线程时指定销毁时机,有兴趣的自行百度