1. 线程
每个进程
都有自己的数据段、代码段、堆栈段。线程
具有独立的栈、CPU寄存器状态。但一个进程下的所有线程共享其进程的所有资源:
- 打开的文件
- 内存页面
- 信号标识
- 动态分配的内存
一个进程至少需要一个线程作为他的指令执行体。
操作系统可以按照时间片轮转的方式调度多个进程,一个进程也会在自己所分配的时间片内按调度方式调度自己的各个线程。
2. 线程分类
- 用户级线程:主要解决上下文切换,调度过程由用户决定(启动、关闭)
- 内核级线程:由内核调度机制管理、用户无法管理
用户级线程要绑定内核级线程,一个进程中的内核级线程会被分配到固定的时间片,用户级进程分配的时间片以内核级线程为准。
3. 线程创建
3.1 pthread_create
#include <pthread.h>
int pthread_create(pthread_t *restrict tdip,
const pthread_attr_t *restrict attr,
void*(*start_rtn)(void*),
void *restrict arg
);
/*
tdip:线程标识符
attr:线程属性
start_rtn:线程运行函数起始地址
arg:传递给线程运行函数的参数
*/
举个栗子:主线程创建两个线程代表兔子和乌龟,看谁能先跑完10m.
#include <pthread.h>
#include <stdio.h>
#include <time.h>
void* th_fun(void *args)
{
int dis = (int)args;//直接传的就是10,不需解引用;
for(int loop = 0; loop<= dis; ++loop)
{
printf("%x run %d;\n", pthread_self(), loop);
sleep(1);
}
}
int main()
{
pthread_t rabbit, turtle;
if(pthread_create(&rabbit, NULL, th_fun, (void*)10) != 0)
{
perror("pthread_crate error.\n");
}
if(pthread_create(&turtle, NULL, th_fun, (void*)10) != 0)
{
perror("pthread_crate error.\n");
}
/* 调用pthread_join, 阻塞,直到rabbit和turtle运行结束 */
pthread_join(rabbit, NULL);
pthread_join(turtle, NULL);
printf("control thread_id:%x", pthread_self());
return 0;
}
3.2 编译链接 -lpthread
gcc src -lpthread
3.2 线程内存模型
4. 线程终止
线程被创建,也可以被终止。当然比如进程
终止的时候线程
肯定就终止了,但线程也可以主动终止。
通常一个线程终止后所占用的资源不会立即释放,所以需要主控线程调用pthread_join()
来等待线程结束并释放资源。
4.1 主动终止
1、线程的执行函数调用return语句
2、调用pthread_exit()函数
void pthread_exit(void *retval);
/* 参数 */
retval:线程的返回值指针,可由其他函数和pthread_join()来获取
4.2 被动终止
线程可以被同一进程的其他线程取消,其他线程调用
int pthread_cancle(pthread_t tid);
/* 参数 */
tid: 被终止的线程标识符
4.3 等待阻塞线程函数pthread_join()
int pthread_join(pthread_t tid, void **thread_retval);
/* 入参: */
tid: 要等待的线程的线程id
thread_retval: 为入参,获取子线程运行函数的返回值
4.4 主控线程获取子线程的返回值
以下两种函数的返回值均可以由pthread_join()
获取.
4.4.2 子线程运行函数return
4.4.3 子线程终止函数pthread_exit
4.4.1 将主线程的变量通过指针方式传递给子线程
在介绍前两种方法之前,我们先来看一下传递参数的方式.
pthread_create()
函数中的args参数是传递给子线程的,在子线程中通过指针方式修改该值可以修改主线程的变量.
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
typedef struct
{
int num;
char name[10];
}T_MAN;
void *th_fun(void *args)
{
T_MAN *man = (T_MAN*)args;
man->num = 1;
memcpy(man->name, "XiaoMing", 9);
return (void*)man;
}
int main()
{
int num = 10;
pthread_t thid;
T_MAN man= {0,""};
if(pthread_create(&thid, NULL, th_fun, (void*)&man) != 0)
{
perror("Create thread error!\n");
}
printf("Main Control:man.num = %d,man.name = %s\n",man.num, man.name);
sleep(1);
printf("Main Control after Son:man.num = %d,man.name = %s\n",man.num, man.name);
return 0;
}
4.4.2 子线程运行函数返回
利用4.4.1中的方式已经获取到子线程的值,但这种方式有点类似于常见的函数调用。
另外一种方式是:
主控线程可以使用pthread_join()
获取这个void*
类型的指针。
但是由于返回的是指针,所以不能返回局部变量的地址,
所以这种方式获取子线程返回值的数据量有限,且在强转时需要保证和指针长度相同(不同系统有差异)
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
typedef struct
{
long long age;
char name[10];
}T_MAN;
void *th_fun(void *args)
{
T_MAN man= {0,""};
man.age = 1;
memcpy(man.name, "XiaoMing", 9);
return (void*)man.age;
}
int main()
{
pthread_t thid;
if(pthread_create(&thid, NULL, th_fun, NULL) != 0)
{
perror("Create thread error!\n");
}
int *num= NULL;
pthread_join(thid, (void**)&num);
printf("num = %d\n", num);
return 0;
}
这种方式可能适合于全局变量,并加锁保护.
4.4.2 获取pthread_exit返回值
这种方式和子线程运行函数return
的方式很类似,同样由pthread_join()
函数获取.
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
typedef struct
{
long long age;
char name[10];
}T_MAN;
void *th_fun(void *args)
{
T_MAN man= {0,""};
man.age = 1;
memcpy(man.name, "XiaoMing", 9);
pthread_exit((void*)man.age);
}
int main()
{
pthread_t thid;
if(pthread_create(&thid, NULL, th_fun, NULL) != 0)
{
perror("Create thread error!\n");
}
int *num= NULL;
pthread_join(thid, (void**)&num);
printf("num = %d\n", num);
return 0;
}
4.5 线程清理和控制函数
以下两个函数为一对出现,两个函数的调用表示执行一个线程清理函数。push
为压入,pop
为出栈。
void pthread_cleanup_push(void(*rtn), void *arg);
void pthread_cleanup_pop(int bExecute);
/* 参数说明*/
rtn:清理函数指针
arg:调用线程清理函数的参数
bExecute:为1时执行线程清理函数,为0时不执行
#include <pthread.h>
void th_clean(void *args)
{
char *str = (char*)args;
print("CleanUp:%s", str);
}
void *th_fun(void *args)
{
int bExe = (int)args;
printf("threadId:%d\n", ptherad_self());
pthread_cleanup_push(th_clean, "FirstClean");
pthread_cleanup_push(th_clean, "SecondClean");
pthread_cleanup_pop(bExe);
pthread_cleanup_pop(bExe);
}
int main()
{
pthread_t tid;
if(ptherad_create(&tid, NULL, th_fun, (void*)1) != 0)
perror("thread cretae error\n");
ptherad_join(tid);
return 0;
}
4.6 线程属性初始化和销毁、获得线程分离属性
4.6.1 线程属性初始化和销毁
在前面pthread_create中,线程属性指我们都是传的空指针,下面将演示一下这个参数:
/*
功能:初始化和销毁线程属性,初始化和系统提供的函数,无法对其进行定制
*/
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destory(pthread_attr_t *attr);
/* 成功返回0,失败返回错误编号 */
下面就是线程属性结构的结构体,其中最重要的就是第一个: 线程的分离状态.
typedef struct
{
int etachstate; //线程的分离状态
int schedpolicy; //线程的调度策略
structsched_parm schedparam; //线程的调度参数
int scope; //线程的作用域
int inheritsched; //线程的继承性
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程栈的设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
4.6.2 获取和设置分离属性
线程属性中最重要的就是分离属性
/* 获取和设置线程分离属性 */
线程分离属性的取值
// PTHREAD_CREATE_JOINABLE 默认值
// PTHREAD_CREATE_DETACHED 分离状态启动线程
int pthread_attr_getdetachstat(const pthread_attr_t *restrict attr, int *detach stat);
int pthread_attr_setdetachstat(const pthread_attr_t *attr, int detach stat);
线程分离属性的作用:
以默认方式启动的线程,结束后不会马上释放系统资源,要在主控线程pthread_join()后才会释放;
以分离状态启动的线程可以在线程结束后自动释放所有系统资源;
4.6.3 线程属性举例
#include <pthread.h>
#include <stdio.h>
void* th_fun(void *args)
{
printf("Enter th_fun;\n");
return (void*)0;
}
int main()
{
pthread_t tid_join, tid_detach;
pthread_attr_t th_attr;
//线程属性初始化
pthread_attr_init(&th_attr);
/*以分离属性的默认值启动*/
if(pthread_create(&tid_join, &th_attr, th_fun, (void*)0) != 0)
perror("pthread_create error!\n");
pthread_join(tid_join, NULL);
//设置线程分离属性为detach
pthread_attr_setdetachstat(&th_attr, PTHREAD_CREATE_DETACHED);
/*以分离属性的分离状态启动*/
if(pthread_create(&tid_detach, &th_attr, th_fun, (void*)0) != 0)
perror("pthread_create error!\n");
pthread_join(tid_detach, NULL);
pthread_attr_destory(&th_attr);
return 0;
}
4.7 进程和线程控制的对比
4.8 线程的状态切换
其中:
Running->Blocked(锁定)
是线程在获取互斥锁、读写锁时进入。锁被释放后,等待该锁的所有线程有一个获得该锁,进入Runnable
Running->Blocked(等待)
是将线程自己插入到等待队列,等待其他线程唤醒,唤醒后进入锁定状态.
5. 线程同步和互斥
线程互斥
多个线程对同一个共享资源进行操作时,如果不考虑互斥,那么这个共享资源的最终结果可能不是我们所预期的.
解决互斥的方式:
- 互斥锁
- 读写锁
- 信号量
线程同步
同步表示的是一种相互依赖关系,对共享资源操作,必须等待上一步操作结果结束.
线程同步建立在互斥的基础上,但又要考虑线程先后执行关系。
5.1 互斥锁pthread_mutex_t
互斥锁:在同一时刻只能有一个线程掌握某个互斥锁,若其他线程想要获得互斥锁将会被挂起.
5.1.1 互斥锁函数接口
1. 互斥锁定义和销毁
/* 定义互斥锁 */
pthread_mutex_t mutex;
/* 功能:初始化和销毁互斥锁,成功返回0*/
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr *mutex_attr);
int pthread_mutex_destory(pthread_mutex_t *mutex);
- 参数
mutex
:互斥锁
mutex_attr
:互斥锁创建方式,可以是以下几种:
快速互斥锁:PTHREAD_MUTEX_INITIALIZER(常规互斥锁)
递归互斥锁:PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP(递归互斥锁)
一个线程获得锁后,另外线程仍继续获得锁,内部计数.
检错互斥锁:PTHREAD_ERROR_CHECK_MUTEX_INITIALIZER(检错互斥锁)
一个线程获得锁后,另外线程再获得返回错误.
2. 获取互斥锁和解锁
/* 获取锁和解锁 */
//获取锁,拿不到则被阻塞
int pthread_mutex_lock(pthread_mutex *mutex);
//获取锁,拿不到则返回错误信息
int pthread_mutex_trylock(pthread_mutex *mutex);
//释放锁,
int pthread_mutex_unlock(pthread_mutex *mutex);
3. 互斥锁属性
前面在创建互斥锁时,对互斥锁属性
可以传入一个NULL
或一个宏值
。但其实互斥锁属性
是一个结构体,互斥锁属性结构体
初始化:
pthread_mutexattr mutex_attr;
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destory(pthread_mutexattr_t *attr);
互斥锁属性结构体
中比较重要的是进程共享属性和互斥锁类型
进程共享属性
/*
功能:初始化属性
参数:
attr 互斥锁属性
pshared 进程共享属性值
*/
pthread_mutexattr mutex_attr;
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr);
进程共享属性可以是以下两个值:
PTHREAD_PROCESS_PRIVATE:只能被一个进程中的两个线程互斥
PTHREAD_PROCESS_SHARED:可被不同进程中的线程互斥
互斥锁类型
互斥锁类型
和互斥锁初始化时传入的几个宏值有相似之处:
pthread_mutexattr_t mutex_attr;
int pthread_mutexattr_gettype(pthread_mutexattr_t *mutex_attr, OUT int *type);
int pthread_mutexattr_settype(pthread_mutexattr_t *mutex_attr, IN int type);
互斥锁类型可以有以下几种类型:
PTHREAD_MUTEX_NORMAL:一个线程获得锁后,第二个线程阻塞
PTHREAD_MUTEX_RECURSIVE:一个线程获得锁后,其他线程仍可获得,不会被阻塞,内部计数.
PTHREAD_MUTEX_ERRORCHECK:一个线程获得锁后,其他线程获得会出错.
5.1.2 互斥锁例子
现在可以有亲情账户,设置亲情账户后两个人都可以进行存取款,但只对应一个账户.
银行账户
#ifndef __ACCOUNT_H_
#define __ACCOUNT_H_
#include <pthread.h>
#include <stdio.h>
typedef struct
{
unsigned int id;
double money;
pthread_mutex_t actMutex;
}Account;
/* 账户初始化 */
void InitAccount(Account* act)
{
if(act == NULL) return;
act->id = 123456789;
act->money = 10000;
pthread_mutex_init(act, NULL)
}
/* 取钱 */
void withDraw(Account* act, double val)
{
if(act == NULL) return;
pthread_mutex_lock(&act->actMutex);
if(val<0 || val>act->money)
{
printf("withDraw money is 0.00\n");
pthread_mutex_unlock(&act->actMutex);
return;
}
act->money -= val;
printf("withDraw money is %u\n", val);
pthread_mutex_unlock(&act->actMutex);
}
/* 存钱 */
void Deposite(Account *act){};
#endif
两个取款线程
#include <pthread.h>
#include <stdio.h>
#include "Account.h"
Account Love;
void* Opt(void *args)
{
int money = (int)args;
sleep(1);
WithDraw(&Love, args);
}
int main()
{
pthread_t boy, girl;
pthread_attr_t th_attr;
InitAccount(&Love);
pthread_attr_init(&th_attr);
pthread_attr_setdetachstat(&th_attr, PTHREAD_CREATE_DETACHED);
if(pthread_create(&boy, &th_attr, Opt, (void*)1000) != 0)
perror("create error");
if(pthread_create(&girl, &th_attr, Opt, (void*)1000) != 0)
perror("create error");
pthread_attr_deatory(&th_attr);
return 0;
}
5.2 读写锁pthread_rwlock_t
互斥锁
缺少读的并发性,使用读写锁
时同时读不会被阻塞,提升效率。
读写锁
是同一把锁,只是线程在获得锁时可以进行选择,获得读
锁,或者写
锁。
5.2.1 读写锁函数接口
初始化和销毁
#include <pthread.h>
pthread_rwlock_t rwlock;
int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加锁和解锁
#include <pthread.h>
pthread_rwlock_t rwlock;
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//加写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//释放锁
读写锁操作结果
锁1 | 锁2 | 锁2结果 |
---|---|---|
读 | 读 | 成功 |
读 | 写 | 阻塞 |
写 | 读 | 失败 |
写 | 写 | 失败 |
5.2.3 案例
还是按上面存取款的案例
银行账户
#ifndef __ACCOUNT_H_
#define __ACCOUNT_H_
#include <pthread.h>
#include <stdio.h>
typedef struct
{
unsigned int id;
double money;
pthread_rwlock_t act_rwLock;
}Account;
/* 账户初始化 */
void InitAccount(Account* act)
{
if(act == NULL) return;
act->id = 123456789;
act->money = 10000;
pthread_rwlock_init(act->act_rwLock, NULL)
}
/* 取钱 */
void withDraw(Account* act, double val)
{
if(act == NULL) return;
pthread_rwlock_wrlock(&act->actMutex);
if(val<0 || val>act->money)
{
printf("withDraw money is 0.00\n");
pthread_rwlock_unlock(&act->actMutex);
return;
}
act->money -= val;
printf("withDraw money is %u\n", val);
pthread_rwlock_unlock(&act->actMutex);
}
/* 存钱 */
void Deposite(Account *act){};
/* 读余额 */
double GetMoney(Account *act)
{
if(act == NULL) return;
pthread_rwlock_rdlock(act->act_rwLock);
double money = act->money;
pthread_rwlock_unlock(act->act_rwLock);
return money;
}
#endif
两个取款线程
#include <pthread.h>
#include <stdio.h>
#include "Account.h"
Account Love;
void* optWithDraw(void *args)
{
int money = (int)args;
sleep(1);
WithDraw(&Love, args);
}
void optGetMoney()
{
double money = GetMoney(&Love);
printf("money = %u\n", money);
}
int main()
{
pthread_t boy, girl;
pthread_attr_t th_attr;
InitAccount(&Love);
pthread_attr_init(&th_attr);
pthread_attr_setdetachstat(&th_attr, PTHREAD_CREATE_DETACHED);
if(pthread_create(&boy, &th_attr, optWithDraw, (void*)1000) != 0)
perror("create error");
if(pthread_create(&girl, &th_attr, optGetMoney, (void*)0) != 0)
perror("create error");
pthread_attr_destroy(&th_attr);
return 0;
}
5.3 条件变量
5.3.1 条件变量的作用
- 条件变量内部是一个等待队列,放置使用了
wait
等待的线程 - 使用条件变量时需要一个互斥锁,这个互斥锁是为了保护等待队列
- 条件变量允许线程先进入阻塞状态,等待条件发生被唤醒。唤醒时可唤醒一个或多个等待线程.
5.3.2 条件变量函数接口
条件变量初始化
pthread_cond_t cond;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);
条件变量等待操作
#include <pthread.h>
pthread_cond_t cond;
pthread_mutex_t mutex_lock;
pthread_cond_init(&cond, NULL);
pthread_mutex_init(&mutex_lock, NULL);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutexlock);
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutexlock, struct timespec *timeout);
/*
struct timespec
{
time_t tv_sec;
long tv_nsec;
};
*/
条件变量通知操作
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal通知单个线程.
pthread_cond_broadcast通知该等待队列的所有线程.
5.3.3 条件变量案例1
一个线程计算值,一个线程读取值
#include <pthread.h>
#include <stdio.h>
typedef struct
{
int res;
bool isWait;
pthread_cond_t cond;
pthread_rwlock_t rwlock;
pthread_mutex_t mutex_lock;
}T_CONSUME;
void* cal_th(void *args)
{
T_CONSUME *tRes = (T_CONSUME*)args;
tRes->res = 10;
pthread_rwlock_rdlock(&tRes->rwlock);
while(tRes->isWait != 1)
{
pthread_rwlock_unlock(&tRes->rwlock);
usleep(200);
pthread_rwlock_rdlock(&tRes->rwlock);
}
pthread_rwlock_unlock(&tRes->rwlock);
pthread_cond_broadcast(&tRes->cond);
return (void*)0;
}
void* Get_th(void *args)
{
T_CONSUME *tRes = (T_CONSUME*)args;
pthread_rwlock_rdlock(&tRes->rwlock);
tRes ->isWait = TRUE;
pthread_rwlock_unlock(&tRes->rwlock);
pthread_cond_wait(&tRes->cond, &tRes->mutex_lock);
printf("res is %d\n", tRes->res);
pthread_mutex_unlock(&tRes->mutex_lock);//这里会多一个unlcok,见5.3.4
return (void*)0;
}
int main()
{
T_CONSUME tRes;
tRes.isWait = 0;
pthread_cond_init(&tRes.cond, NULL);
pthread_rwlock_init(&tRes.rwlock, NULL);
pthread_mutex_init(&tRes->mutex_lock, NULL);
pthread_t cal,get;
if(pthread_create(&cal, NULL, cal_th, (void*)tRes) !=0)
perror("thread create error!\n");
if(pthread_create(&cal, NULL, cal_th, (void*)tRes) !=0)
perror("thread create error!\n");
pthread_join(cal, NULL);
pthread_join(get, NULL);
pthread_cond_destroy(&tRes.cond);
pthread_rwlock_destroy(&tRes.rwlock);
pthread_mutex_destroy(&tRes.mutex_lock);
}
5.3.4 条件变量等待的过程
pthread_cond_wait(&cond, &mutex_lock);
- unlock(&mutex_lock);
- lock(&mutex_lock);
- 将线程插入到等待队列
- unlock(&mutex_lock);
- 当前线程阻塞,等待其他线程通知
- 唤醒后…lock(&mutex);
- 从等待队列删除线程自己
5.3.5 条件变量案例1
一个线程计算值,多个线程读取值
#include <pthread.h>
#include <stdio.h>
typedef struct
{
int res;
int waitCount;
pthread_cond_t cond;
pthread_rwlock_t rwlock;
pthread_mutex_t mutex_lock;
}T_CONSUME;
void* cal_th(void *args)
{
T_CONSUME *tRes = (T_CONSUME*)args;
tRes->res = 10;
pthread_rwlock_rdlock(&tRes->rwlock);
while(tRes->waitCount<2)
{
pthread_rwlock_unlock(&tRes->rwlock);
usleep(200);
pthread_rwlock_rdlock(&tRes->rwlock);
}
pthread_rwlock_unlock(&tRes->rwlock);
pthread_cond_broadcast(&tRes->cond);
return (void*)0;
}
void* Get_th(void *args)
{
T_CONSUME *tRes = (T_CONSUME*)args;
pthread_rwlock_rdlock(&tRes->rwlock);
tRes->waitCount++;
pthread_rwlock_unlock(&tRes->rwlock);
pthread_cond_wait(&tRes->cond, &tRes->mutex_lock);
printf("res is %d\n", tRes->res);
pthread_mutex_unlock(&tRes->mutex_lock);//这里会多一个unlcok,见5.3.4
return (void*)0;
}
int main()
{
T_CONSUME tRes;
tRes.waitCount = 0;
pthread_cond_init(&tRes.cond, NULL);
pthread_rwlock_init(&tRes.rwlock, NULL);
pthread_mutex_init(&tRes->mutex_lock, NULL);
pthread_t cal,get;
if(pthread_create(&cal, NULL, cal_th, (void*)tRes) !=0)
perror("thread create error!\n");
if(pthread_create(&cal, NULL, cal_th, (void*)tRes) !=0)
perror("thread create error!\n");
pthread_join(cal, NULL);
pthread_join(get, NULL);
pthread_cond_destroy(&tRes.cond);
pthread_rwlock_destroy(&tRes.rwlock);
pthread_mutex_destroy(&tRes.mutex_lock);
}
5.4 线程信号量
- 信号量数据类型:
sem_t
- 信号量加减操作:
sem_post
和sem_wait
- 一个线程即使没有调用
sem_wait
,也可以调用sem_post
.
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, int value);
int sem_destroy(sem_t *sem);
/*@para
@em: 信号量指针
pshared: 进程共享属性. 0不共享,1共享
value: 信号量的初始值
*/
#include <semaphore.h>
int sem_post(sem_t *sem); /* 释放资源,信号量+1 */
int sem_wait(sem_t *sem); /* 获取资源,信号量-1 */
int sem_trywait(sem_t *sem); /* 获取资源,不会阻塞 */
调用sem_wait()
后信号量的值-1
,若此时信号量的值<0,则线程会阻塞,直到其他线程调用sem_post
使得信号量>0.
5.4.1 PV操作案例——取款
#ifndef __ACCOUNT_H_
#define __ACCOUNT_H_
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
typedef struct
{
unsigned int id;
double money;
sem_t sem;
}Account;
/* 账户初始化 */
void InitAccount(Account* act)
{
if(act == NULL) return;
act->id = 123456789;
act->money = 10000;
sem_init(&act->sem, 0, 1);
}
/* 取钱 */
void withDraw(Account* act, double val)
{
if(act == NULL) return;
sem_wait(&act->sem);
if(val<0 || val>act->money)
{
printf("withDraw money is 0.00\n");
sem_post(&act->sem);
return;
}
act->money -= val;
printf("withDraw money is %u\n", val);
sem_post(&act->sem);
}
/* 存钱 */
void Deposite(Account *act){};
#endif
两个取款线程
#include <pthread.h>
#include <stdio.h>
#include "Account.h"
Account Love;
void* Opt(void *args)
{
int money = (int)args;
sleep(1);
WithDraw(&Love, args);
}
int main()
{
pthread_t boy, girl;
pthread_attr_t th_attr;
InitAccount(&Love);
pthread_attr_init(&th_attr);
pthread_attr_setdetachstat(&th_attr, PTHREAD_CREATE_DETACHED);
if(pthread_create(&boy, &th_attr, Opt, (void*)1000) != 0)
perror("create error");
if(pthread_create(&girl, &th_attr, Opt, (void*)1000) != 0)
perror("create error");
pthread_attr_deatory(&th_attr);
sem_destroy(&act->sem);
return 0;
}
5.4.2 PV操作案例——结果计算
一个线程计算,一个线程读取,计算完成前,读取阻塞。
思路:
- 信号量初始化为0
- 读取操作先sem_wait();
- 计算线程计算完成后sem_post();
/* 计算线程函数 */
sem_init(&sem, 0, 0);
void* fun_set(void* args)
{
calc();
sem_post(&sem);
}
/* 读取线程函数 */
void* fun_get(void* args)
{
sem_wait(&sem);
get();
}
5.5 线程和信号
- 每个线程都有自己的信号屏蔽字和信号未决字
- 进程中的所有线程共享信号处理方式
- 进程(not 线程)中的信号是递送到单个线程的
- 定时器是共享资源,进程中的线程共享相同的定时器
- 子线程使用alarm( )设置定时器,信号仍然是发送给主控线程.
5.5.1 定时器
案例:主线程创建子线程后循环睡眠10s,子线程设置2s定时器。观察定时器时间到唤醒的是主控线程并处理.
#include <pthread.h>
#include <stdio.h>
#include <signal.h>
/* 信号处理函数 */
void sighandle(int signo)
{
print("sighandle threadid:%lx\n", pthread_self());
if(signo == SIGALRM)
{
print("timeout\n");
}
alarm(2)
}
/* 子线程函数 */
void* th_fun(void* args)
{
/* 捕获信号 */
if(signal(SIGALRM, sig_handler) == SIG_ERR)
perror("signal sigalarm error!");
alarm(2);//设置定时器
for(int i=0;i<100;++i){
sleep(1);
print("son pthread:%lx\n", pthread_self());
}
return (void*)0;
}
int main()
{
int err;
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if(err = pthread_create(&th, &attr, th_fun, (void*)0) !=0)
perror("create error!!")
while(1)
{
print("main control thread:%lx", pthread_self());
sleep(10);
}
return 0
}