UnixC第十四天
回忆昨天内容
一、并发服务器的实现
多进程实现并发服务器
父进程 子进程 各自负责的任务
信号 头文件 动态库的制作和生成 使用
使用多线程也可以实现服务器的并发
二、基于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);
}
}