回忆昨天内容
一、并发服务器的实现
多进程实现并发服务器
父进程 子进程 各自负责的任务
信号 头文件 动态库的制作和生成 使用
使用多线程也可以实现服务器的并发
二、基于udp的编程实现
模型
实现
服务器 客户端
不需要连接 基于数据包的
recvfrom(2) sendto(2)
三、线程的基础
每个线程有自己的tid tcb
每个线程有自己私有的资源
一个进程中有多个线程 进程中的线程共享进程的资源
进程是资源分配的基本单位 线程是执行的基本单位
获取线程的tid pthread_self(3)
获取进程的pid getpid(2)
线程的创建 pthread_create(3) 私有资源为栈帧
线程执行函数 返回值 形参
线程的终止 汇合 分离
return exit(3)的区别
线程中不要使用exit(3)
pthread_exit(3) 终止当前调用其的线程
pthread_cancel(3)向线程发送取消请求 取消线程终止时终止状态标记为PTHREAD_CANCELED
pthread_detach(3) 线程的分离 分离线程终止时,自动释放资源给系统,不需要其他线程来回收
pthread_join(3) 让进程的一个线程收另外一个线程的资源
今天的内容
一、线程汇合:
pthread_join
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
功能:汇合一个已经终止的线程,等待一个线程终止,回收其资源,如果已经终止,立即返回
参数:thread 指定要被汇合的线程的tid
retval 将目标线程的退出状态码拷贝到*retval指向的地址中
返回值:成功 0
错误 错误码
举例说明 线程的终止 汇合
代码参见 pthread_e.c
二、线程同步:
1)可重入函数
函数只能访问自己栈帧里的资源 不去访问共有资源,这样的函数称为可重入函数
如果这个函数访问了静态局部变量 全局变量和malloc申请的变量,则是不可重入函数
举例说明 多个线程异步访问临界资源产生错误的情况
代码参见 count.c
2)mutex锁
pthread_mutex_t mutex锁类型
mutex锁是一个互斥设备 用于对共享数据的保护
两种类型 一种unlocked:不被任何进程占有
locked:被一个进程占有
任何一个mutex不能同时被两个线程占有
若一个线程要加锁的资源已经被另一个线程锁住,需等待另一个线程解锁
跟类型相关的操作
//静态初始化mutex锁
pthread_mutex_t fastmutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(3)
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);
功能:使用属性初始化mutex指定的锁
参数:
mutex 指定要初始化的锁
mutexattr NULL 缺省属性
返回值 总是0
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:加锁 如果锁处于解锁状态,立即加锁并返回,如果要加的锁被其他线程占用,则阻塞等待其他线程释放锁
参数:
mutex指定要操作的锁
返回值 成功0
错误 非0的错误码 errno被设置
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:尝试加锁 如果锁处于解锁状态,立即加锁并返回,如果要加的锁被其他线程占用,则立即返回EBUSY
参数:
mutex 指定要操作的锁
返回值 成功0
错误 非0的错误码 errno被设置
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:解锁 mutex被假定为被当前线程占用且被锁定的状态
参数:
mutex 指定要操作的锁
返回值 成功0
错误 非0的错误码 errno被设置
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁mutex锁,释放锁住的资源
参数:
mutex 指定要销毁的mutex锁
返回值 成功0
错误 非0的错误码 errno被设置
3)条件变量
什么是条件变量?
是一个同步设备。允许多个线程挂起执行,并且放弃cpu,直到条件被满足。
signal the condition (when the predicate becomes true),
发送信号。条件变为真的时候,发送这个信号
wait for the condition
等待条件变为真
一个条件变量跟一个mutex相关联
pthread_cond_t 条件变量类型
跟条件变量类型相关的操作
#include <pthread.h>
//条件变量的静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
功能:初始化条件变量
参数:
cond:指定要初始化的条件变量
cond_attr:指定条件变量的属性,默认NULL。使用这个属性来初始化条件变量
返回值: 成功0
错误 非0错误码
int pthread_cond_signal(pthread_cond_t *cond);
功能:启用等待条件变为真的所有的线程中的一个线程。如果没有线程等待条件变为真,do nothing
参数:
cond:指定条件变量。从在这个条件变量上等待的线程中取一个线程
返回值:
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:启用等待条件变为真的所有的线程,如果没有线程等待条件变为真,do nothing
参数:
cond:指定条件变量。启动所有在这个条件变量上等待的线程
返回值: 成功0
错误 非0错误码
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
功能:
参数:
cond:指定条件变量
mutex:指定mutex锁
返回值: 成功0
错误 非0错误码
步骤:
1、原子的解锁和等待条件变为真
2、只有条件变量变为真的时候,这个函数才返回。返回来的时候,重新加锁mutex.
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex, const struct timespec *abstime);
功能:指定了一个等待周期,超时就不再等待。
参数:
cond:
返回值: 成功0
错误 非0错误码
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁条件变量
参数:
cond:指定要销毁的条件变量
返回值:
成功 0
错误 非0的错误码
举例说明 使用条件变量实现生产者和消费者的例子
没有生产就不能消费
数据结构 链表
生产者线程 生产一个新的节点,将这个节点插入到链表的头部
消费者线程 从链表的头部取出一个节点,消费这个节点
代码参见 p_c_cond.c
信号量
sem_t 类型
什么是信号量?
跟类型相关的操作
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化匿名信号量。在sem指定的地址空间里初始化。
参数:
sem:指定信号量的地址
shared:0 多线程 非0 多进程
value:信号量的初始值
返回值:
成功 0
错误 -1 errno被设置
Link with -pthread.
sem_destroy(3)
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:销毁一个匿名信号量
参数:
sem:指定要销毁的信号量
返回值:
成功 0
错误 -1 errno被设置
+1/-1
sem_post(3)
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:加1操作。如果semval>0,会唤醒等待semval>0的线程,这些线程在继续获取信号量(-1).
参数:
sem:指定要操作的信号量
返回值:
成功 0
错误 -1 errno被设置
sem_wait(3)
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:减一操作 semval>0 立即返回
semval==0 阻塞,直到另一个线程将其变为大于0为止。
参数:
sem:指定要操作的信号量
返回值:
成功 0
错误 -1 errno被设置
举例说明 使用环状队列实现生产者和消费者的例子
代码参见 p_c_sem.c
pthread_e.c
#include<t_stdio.h>
#include<pthread.h>
#include<unistd.h>
void *handle(void *arg){
printf("handle running...\n");
return ((void *)2);
}
void *handle1(void *arg){
printf("handle1 running...\n");
pthread_exit((void *)5);
}
void *handle2(void *arg){
while(1){
sleep(1);
printf("handle2 running...\n");
}
pthread_exit((void *)5);
}
int main(void){
pthread_t tid;
void *ret;
//创建一个新的线程
pthread_create(&tid,NULL,handle,NULL);
//等待新线程终止 汇合新的线程
pthread_join(tid,&ret);
printf("handle exit code...%d\n",(int)ret);
//再创建一个新的线程,用pthread_exit
pthread_create(&tid,NULL,handle1,NULL);
//等待新线程终止 汇合新的线程
pthread_join(tid,&ret);
printf("handle1 exit code...%d\n",(int)ret);
//再创建一个新的线程,用pthread_cancel;
pthread_create(&tid,NULL,handle2,NULL);
sleep(4);
//给tid线程发送取消请求
pthread_cancel(tid);
//等待新线程终止 汇合新的线程
pthread_join(tid,&ret);
if(PTHREAD_CANCELED==ret)
//printf("handle2 exit code...%d\n",(int)ret);
printf("handle2 exit code...%d\n",(int)PTHREAD_CANCELED);
return 0;
}
count.c
#include<stdio.h>
#include<pthread.h>
//定义锁类型的变量,静态初始化
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
//线程异步,所以最后val并不是10000
int val=0;
void *handle(void *arg){
int temp;
for(int i=0;i<5000;i++){
//加锁
pthread_mutex_lock(&mutex);
temp=val;
temp++;
printf("my tid=%ld,temp=%d\n",pthread_self(),temp);
val=temp;
//解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(){
pthread_t tid,pid;
//创建两个线程
pthread_create(&tid,NULL,handle,NULL);
pthread_create(&pid,NULL,handle,NULL);
//等待线程汇合
pthread_join(tid,NULL);
pthread_join(pid,NULL);
//销毁mutex锁
pthread_mutex_destroy(&mutex);
return 0;
}
p_c_sem.c
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<time.h>
#include<stdlib.h>
#include <semaphore.h>
typedef int que_t[6];
que_t q;//定义了一个循环队列
sem_t p,c;//可生产的数量和可消费的数量
//生产者线程
void *product(void *arg){
int i=0;//数组下标
while(1){
//判断是否可以生产
sem_wait(&p);
q[i]=rand()%1000+1;
printf("p:%d\n",q[i]);
sem_post(&c);
i=(i+1)%6;
sleep(rand()%5+1);
}
}
//消费者线程
void *consume(void *arg){
int r=0,tmp;
while(1){
sem_wait(&c);//判断是否可以消费
tmp=q[r];
q[r]=-1;
sem_post(&p);
printf("c:%d\n",tmp);
r=(r+1)%6;
sleep(rand()%5+1);
}
}
int main(){
srand(time(NULL));
//初始化信号量的值
sem_init(&p,0,6);
sem_init(&c,0,0);
pthread_t pid,cid;
//创建两个线程 一个是生产者 一个是消费者
pthread_create(&pid,NULL,product,NULL);
pthread_create(&cid,NULL,consume,NULL);
//等待线程的汇合
pthread_join(pid,NULL);
pthread_join(cid,NULL);
sem_destroy(&p);
sem_destroy(&c);
return 0;
}
p_c_cond.c
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<time.h>
#include<stdlib.h>
#include <semaphore.h>
//声明条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//定义一个mutex锁
pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;
//链表定义
typedef struct node{
int data;
struct node *next;
}node_t;
typedef node_t *list_t;
list_t head=NULL;//空链表
//生产者线程,生产一个节点,插入链表头部
void *product(void *arg){
node_t *new;
while(1){
//生产节点
new=(node_t *)malloc(sizeof(node_t));
new->data=rand()%1000+1;
new->next=NULL;
printf("p:%d\n",new->data);
//插入链表头部
//加锁
pthread_mutex_lock(&mutex);
new->next=head;
head=new;
//解锁
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
sleep(rand()%5+1);
}
}
//消费者线程,从链表头部取出一个节点,消费节点
void *consume(void *arg){
node_t *temp;
while(1){
//从链表头部取出一个节点
//加锁
pthread_mutex_lock(&mutex);
/* if(head->next==NULL)
//等待生产,等待head->next不为空
pthread_cond_wait(&cond,&mutex);
else{
temp=head->next;
head->next=temp->next;
}*/
while(head==NULL)
//等待生产,等待head->next不为空
pthread_cond_wait(&cond,&mutex);
temp=head;
head=head->next;
//解锁
pthread_mutex_unlock(&mutex);
//消费该节点
printf("c:%d\n",temp->data);
free(temp);
sleep(rand()%5+1);
}
}