linux编程学习 03 线程
线程
- 进程是资源管理的最小单位,线程是程序执行的最小单位
- 每个进程都有自己的数据段,代码段和堆栈段。线程通常叫做轻型的进程,他包含独立的栈和cpu寄存器状态,线程是进程的一条执行路径,每个线程共享其所属进程的所有资源,包括打开的文件,内存页面,信号标识及动态分配的内存
- 因为线程和进程比起来很小,所以相对来说,线程花费更少的cpu资源
- 在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持多处理器,并且减少进程上下文切换的开销。
进程与线程的关系
- 线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同已进程所产生的线程共享同一用户内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。一个进程至少需要一个线程作为他的指令执行体。进程管理着资源(比如cpu,内存,文件等)。而将线程分配到某个cpu上执行。
- 进程默认会有一个线程,称为主控线程
- 进程进入running状态时再对进程下的线程分配运行时间,同一时间只有一个线程在运行
线程分类
线程按照其调度者可分为用户级线程和内核级线程两种
- 用户级线程:主要解决上下文切换问题,其调度过程由用户决定
- 内核级线程:有内核调度机制实现
- 现在大多数操作系统都采用用户级线程和内核级线程并存的方法
- 用户线程要绑定内核级线程运行,一个进程中的内核级线程会分配到固定的时间片,用户级线程分配的时间片以内核线程为准
- 默认情况下用户级线程和内核级线程是一对一,也可以多对一,但是这样实时性会较差。
- 当cpu分配给线程的时间片用完后但线程没有执行完毕,此时线程会从运行状态切换到就绪状态,将cpu让给其他进程使用
linux线程实现
- 以下线程均为用户级线程,在linux中,一般采用pthread线程库实现线程的访问与控制,由POSIX提出,具有良好的移植性
- linux线程程序编译需要在gcc上链接库pthread
线程标识
- 每个进程内部的不同线程都有自己的唯一标识
- 线程标识只在它所属的进程环境中有效
- 显示标识是pthread_t数据类型
int pthread_equal(pthread_t,pthread_t)
- 返回:相等返回0,否则返回非0
pthread_t pthread_self(void);
- 返回:调用线程的线程id
- 头文件
#include <pthread.h>
线程创建
int pthread_create(pthread_t *retrict tidp,const pthread_attr_t *restrict attr,void *(*start_rtn)(void*),void* restrict arg);
- 返回:成功返回0,失败返回错误编号
- 头文件
#include <pthread.h>
参数
- tidp:线程标识符指针
- attr:线程属性指针
- start_rtn:线程运行函数的起始地址
- arg:传递给线程运行函数的参数
- 新线程从start_rtn函数的地址开始执行
- 不能保证新线程和调用线程的执行顺序
- 示例
typedef struct
{
int start;
int end;
}for_length;
int main(void)
{
pthread_t thread1_id,thread2_id;
int res = 0;
// 传递单个参数
if((res = pthread_create(&thread1_id,NULL,thread_func,(void*)8)) != 0)
{
perror("create thread1 error");
return 1;
}
if((res = pthread_create(&thread2_id,NULL,thread_func,(void*)6)) != 0)
{
perror("create thread2 error");
return 1;
}
// 这里要把进程阻塞,不然进程结束,线程也会结束
// sleep(30);
// 主控线程调用pthread_join,自己会阻塞,直到thread1和thread2结束才会运行
pthread_join(thread1_id,NULL);
pthread_join(thread2_id,NULL);
// 传递多个参数时,可以使用结构体
for_length f1 = {1,10},f2 = {1,8};
if((res = pthread_create(&thread1_id,NULL,thread_func2,(void*)(&f1))) != 0)
{
perror("create thread1 error");
return 1;
}
if((res = pthread_create(&thread2_id,NULL,thread_func2,(void*)(&f2))) != 0)
{
perror("create thread2 error");
return 1;
}
pthread_join(thread1_id,NULL);
pthread_join(thread2_id,NULL);
return 0;
}
/**
* 接收单个参数
* */
void* thread_func(void* par)
{
// 注意因为传递时,是直接把常量的值转换成地址,一般情况下 par = 0x7fffa76962f0 指针变量>是存放的地址,但是这里的情况是 par = 0x000000000006,所以直接转换就可以了
int times = (int)par;
for(int i = 0;i < times;i++)
{
sleep(1);
printf("I am thread %li;i = %d\n",pthread_self(),i);
}
return (void*)NULL;
}
/**
* 接收多个参数
* */
void* thread_func2(void* par)
{
// 转换成结构体指针
for_length* times = (for_length*)par;
for(int i = times->start;i < times->end;i++)
{
sleep(1);
printf("I am thread %li;i = %d\n",pthread_self(),i);
}
return (void*)NULL;
}
终止方式
主动终止
- 线程的执行函数中调用return语句
- 调用pthread_exit
被动终止
- 线程可以被同一进程的其他进程取消,其他线程调用pthread_cancel(pthid)
int pthread_cancel(pthread_t tid);
void pthread_exit(void *retval);
int pthread_join(pthread_t th,void** thread_return);
- 返回值:成功返回0,否则返回错误编号
- 头文件:
#include <pthread.h>
pthread_cancel
- 线程可以被同一进程的其他线程取消,tid为终止的线程标识符
pthread_exit
- retval:pthread_exit调用者线程的返回值,可由其他函数和pthread_join来检测获取
- 线程退出时使用函数pthread_exit,是线程的主动行为
- 由于一个线程中的多个线程共享数据段,因此通常在线程退出后,退出线程所占用的资源并不会随线程结束而释放。所以需要pthread_join函数来等待线程结束,类似于wait系统调用
pthread_join
- th:被等待线程的标识符
- thread_return:用户定义指针,用来存储被等待进程的返回值
- 示例
typedef struct
{
int id;
char name[10];
}user;
void* thread_func(void* data);
int main(void)
{
pthread_t thread_id;
int res = 0;
user u = {1,"jin"};
if((res = pthread_create(&thread_id,NULL,thread_func,(void*)&u)) != 0)
{
perror("create thread error");
}
// 指针变量接收要回传的数据
void* join_res;
// 要传递的是指针变量的地址
pthread_join(thread_id,&join_res);
printf("id = %d;name = %s\n",((user*)join_res)->id,((user*)join_res)->name);
return 0;
}
void* thread_func(void* data)
{
// 先转换指针
user* u = (user*)data;
printf("id = %d;name = %s\n",u->id,u->name);
u->id = 6;
return (void*)u;
}
线程清理和控制函数
void pthread_cleanup_push(void (*rtn)(void*),void* arg);
void pthread_cleanup_pop(int execute);
- 返回:成功返回0,否则返回错误编号
- 头文件
#include <pthread.h>
参数
- rtn:清理函数指针
- arg:调用清理函数传递的参数
- execute:值1时执行线程清理函数,值0时不执行线程清理函数
触发线程调用清理函数动作
- 调用pthread_exit
- 响应取消请求
- 用非零execute参数调用thread_cleanup_pop时
- 示例
int main(void)
{
pthread_cleanup_push(cleanup_func,(void*)6);
// 传入非0才会执行
pthread_cleanup_pop(-1);
return 0;
}
void cleanup_func(void* v)
{
printf("I am cleanup_func;i = %d;\n",(int)v);
}
进程,线程启动和终止方式的比较
进程 | 线程 |
---|---|
fork() | pthread_create() |
return/exit()/_exit() | return/pthread_exit() |
wait() | pthread_join() |
atexit() | pthread_cleanup_push()/pthread_cleanup_pop() |
线程属性初始化和销毁
int pthread_attr_init(pthread_attr_t* attr);
int pthread_attr_destroy(pthread_attr_t* attr);
- 返回:成功返回0,否则返回错误编号
- 头文件
#include <pthread.h>
- 线程属性结构体
typedef struct
{
int etachstate; // 线程的分离状态
int schedpolicy; // 线程调度策略
structsched_param schedparam; // 线程的调度参数
int inheritsched; // 线程的继承性
int scope; // 线程的作用域
size_t guardsize; // 线程栈末尾的警戒缓冲区大小
int stackaddr_set; // 线程的栈设置
void* stackaddr; // 线程栈的位置
size_t stacksize; // 线程栈的大小
}pthread_attr_t;
设置和获得分离属性
int pthread_attr_getdetachstat(const pthread_attr_t*restrict attr,int* detachstate);
int pthread_attr_setdetachstat(const pthread_attr_t* attr,int detachstate);
- 返回:成功返回0,出错返回错误编号
detachstate取值
- PTHREAD_CREATE_JOINABLE(默认值) 正常启动线程
- PTHREAD_CREATE_DETACHED 以分离状态启动线程
- 以默认方式启动的线程,在线程结束后不会自动释放占有的系统资源,要在主控线程中调用pthread_join后才会释放
- 以分离状态启动的线程,在线程结束后会自动释放所占有的系统资源
- 分离属性在网络通讯中使用的较多
- 示例
int main(void)
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_t thread_id;
int res = 0;
if((res = pthread_create(&thread_id,&attr,pthread_func,(void*)6)) != 0)
{
perror("create thread error");
return 1;
}
// 使用分离状态启动进程时,pthread_join无效,看不到输出结果,需要sleep才能看到输出结果
pthread_join(thread_id,NULL);
sleep(1);
return 0;
}
void* pthread_func(void* data)
{
printf("child thread;data = %d\n",(int)data);
return (void*)NULL;
}
线程的同步和互斥
线程同步
- 是一个宏观的概念,在微观上线程的相互排斥和线程的先后执行的约束问题
解决同步方式
- 条件变量
- 进程信号量
线程互斥
- 线程执行的相互排斥
解决互斥方式
- 互斥锁
- 读写锁
- 线程信号量
线程互斥-互斥锁
- 互斥锁(mutex)是一种简单的加锁的方法来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的进程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源。则该线程挂起,直到上锁的线程释放互斥锁为止
互斥锁数据类型
- pthread_mutex_t
互斥锁创建与销毁
int pthread_mutex_init(pthread_mutex_t*restrict mutex,const pthread_mutexattr_t* mutexattr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
- 返回:成功返回0,否则返回错误编号
- 头文件
#include <pthread.h>
参数
- mutex:互斥锁
mutexattr:互斥锁创建方式
PTHREAD_MUTEX_INITIALIZER
- 快速创建互斥锁
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
- 创建递归互斥锁
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
- 创建检错互斥锁
互斥锁上锁和解锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
- 功能:上锁,拿不到锁阻塞
int pthread_mutex_trylock(pthread_mutex_t* mutex);
- 功能:上锁,拿不到锁阻塞
int pthread_mutex_unlock(pthread_mutex_t* mutex);
- 功能:释放锁
- 返回:成功返回0,出错返回出错码
参数
- mutex:互斥锁
- 示例
typedef struct
{
int deposit;
pthread_mutex_t lock;// 把锁与变量联系在一起
}Account;
typedef struct
{
char name[8];
int amount;
Account* account;
}User;
void* withdraw(void*);
void* withdraw(void*);
int main(void)
{
Account account = {88888};
pthread_mutex_init(&account.lock,NULL);
User user1 = {"jin1",88888,&account};
User user2 = {"jin2",88888,&account};
pthread_t thread1_id,thread2_id;
int res = 0;
if((res = pthread_create(&thread1_id,NULL,withdraw,(void*)&user1)) != 0)
{
perror("create thread error");
}
if((res = pthread_create(&thread2_id,NULL,withdraw,(void*)&user2)) != 0)
{
perror("create thread error");
}
pthread_join(thread1_id,NULL);
pthread_join(thread2_id,NULL);
printf("account's deposit %d\n",account.deposit);
return 0;
}
void* withdraw(void* user)
{
User* u = (User*)user;
// 加锁,其他线程到这里加锁时会阻塞住,等解锁后才会继续运行
pthread_mutex_lock(&u->account->lock);
sleep(1);
if(u->amount > u->account->deposit)
{
printf("sorry %s;your balance is not enough\n",u->name);
return (void*)NULL;
}
printf("%s => %d\n",u->name,u->amount);
u->account->deposit -= u->amount;
// 解锁,解锁后其他线程才会继续运行
pthread_mutex_unlock(&u->account->lock);
return (void*)NULL;
}
互斥锁进程共属性操作
int pthread_mutexattr_getpshared(const pthread_mutextattr_t*restrict attr,int*restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t* attr,int pshared);
- 返回:成功返回0,出错返回错误编号
参数
- attr:互斥锁熟悉
pshared
PTHREAD_PROCESS_PRIVATE(默认情况)
- 锁只能用于一个进程内部的两个线程进行互斥
PTHREAD_PROCESS_SHARED
- 锁能用于两个不同进程中的线程进行互斥
互斥锁类型操作
int pthread_mutexattr_gettype(const pthread_mutexattr_t*restrict attr,int*restrict type);
int pthread_mutexattr_settype(const pthread_mutexattr_t*restrict attr,int type);
- 返回:成功返回0,出错返回错误编号
参数
- attr:互斥锁属性
type:互斥锁类型
标准互斥锁:PTHREAD_MUTEX_NORMAL
- 第一次上锁成功,第二次上锁会阻塞
递归互斥锁:PTHREAD_MUTEX_RECURSIVE
- 第一次上锁成功,第二次以后上锁还是成功,内部计数
检查互斥锁:PTHREAD_MUTEX_ERRORCHECK
- 第一次上锁成功,第二次上锁会出错
- 默认互斥锁:PTHREAD_MUTEX_DEFAULT(同标准互斥锁)
- 示例
int main(int argc,char* argv[])
{
if(argc < 2)
{
printf("incorrect parameter\n");
return 1;
}
// 初始化属性变量
pthread_mutexattr_t attr;
// 设置属性
if(strcmp(argv[1],"normal") == 0)
{
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_NORMAL);
}
else if(strcmp(argv[1],"recursive") == 0)
{
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
}
else if(strcmp(argv[1],"errorcheck") == 0)
{
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_ERRORCHECK);
}
else
{
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_DEFAULT);
}
pthread_mutex_t lock;
// 使用设置的属性初始化锁
pthread_mutex_init(&lock,&attr);
if(pthread_mutex_lock(&lock) != 0)
{
printf("first lock error\n");
}
else
{
printf("first lock\n");
}
if(pthread_mutex_lock(&lock) != 0)
{
printf("secound lock error\n");
}
else
{
printf("second lock\n");
}
// 销毁锁
pthread_mutex_destroy(&lock);
pthread_mutexattr_destroy(&attr);
return 0;
}
线程互斥-读写锁
- 线程使用互斥锁缺乏都并发性
- 当读操作较多,写操作较少时,可使用读写锁提高线程读并发性
读写数据锁类型
- pthread_rwlock_t
读写锁创建和销毁
int pthread_rwlock_init(pthread_rwlock_t*restrict rwlock,const pthread_rwlockattr_t*restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
- 头文件
#include <pthread.h>
参数
- rwlock:读写锁
- attr:读写锁属性
读写锁加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
- 功能:加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
- 功能:加写锁
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
- 功能:释放锁
- 返回:成功返回0,出错返回错误编号
参数
- rwlock:读写锁
读写锁特性
- 读和读:不互斥,两个都会成功
- 读和写:互斥,第二个会阻塞
- 写和写:不排斥,第二会失败
- 写和读:不互斥,第二个会失败
- 示例
int main(int argc,char *argv[])
{
if(argc < 3)
{
printf("incorrect parameter\n");
return 1;
}
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock,NULL);
if(strcmp(argv[1],"r") == 0)
{
if(pthread_rwlock_rdlock(&rwlock) != 0)
{
printf("first read lock fail\n");
}
else
{
printf("first read lock success\n");
}
}
else
{
if(pthread_rwlock_wrlock(&rwlock) != 0)
{
printf("first write lock fail\n");
}
else
{
printf("first write lock success\n");
}
}
if(strcmp(argv[2],"r") == 0)
{
if(pthread_rwlock_rdlock(&rwlock) != 0)
{
printf("second read lock fail\n");
}
else
{
printf("second read lock success\n");
}
}
else
{
if(pthread_rwlock_wrlock(&rwlock) != 0)
{
printf("second write lock fail\n");
}
else
{
printf("second write lock success\n");
}
}
// 注意上面锁了两次,所以这里要解锁两次
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_destroy(&rwlock);
return 0;
}
线程同步-条件变量
- 互斥锁的缺点是它只有两种状态:锁定和非锁定
- 条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足
- 条件变量内部是一个等待队列,放置等待的线程,线程在条件变量上等待和通知,互斥锁来保护等待队列(对等待队列上锁),条件变量通常和互斥锁一起使用。
- 条件变量运行线程等待特定条件发生,当条件不满足时,线程通常先进入阻塞状态,等待条件发生变化。一旦它的某个线程改变了条件,可唤醒一个或多个阻塞的线程
- 具体判断条件还需用户给出
条件变量数据类型
- pthread_cond_t
条件变量的创建和销毁
int pthread_cond_wait(pthread_cond_t*restrict cond,pthread_mutex_t*restrict mutex);
int pthread_cond_timewait(pthread_cond_t*restrict cond,pthread_mutex_t*restrict mutex,const struct timespec*restrict timeout);
- 返回:成功返回0,出错返回错误变化
- 头文件
#include <pthread.h>
- timespec结构体
struct timespec
{
time_t tv_sec; // seconds
long tv_nsec; // nanoseconds
}
参数
- cond:条件变量
- mutex:互斥锁
- 互斥锁mutex是对条件变量cond的保护
- 线程由于调用wait函数阻塞,否则释放互斥锁
条件变量通知操作
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
- 返回:成功返回0,出错返回错误编号
参数
- cond:条件变量
- 当条件满足,线程需要通知等待的线程
- pthread_cond_signal函数通知单个线程
- pthread_cond_broadcast函数通知所有线程
pthread_cond_wait函数内部流程
- unlock(&mutex); // 释放锁
- lock(&mutex);
- 将线程自己插入到条件变量的等待队列中
- unlock(&mutex);
- 当前等待的线程阻塞(等待阻塞) <== 等其他线程通知唤醒
- 在唤醒后,lock(&mutex)
- 在等待队列中删除自己
- 示例
typedef struct
{
pthread_mutex_t mutex;
pthread_cond_t cond;
int res;
int is_wait;
}Data;
void* get_v(void*);
void* compare_v(void*);
int main(void)
{
pthread_cond_t cond;
pthread_mutex_t mutex;
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
Data data = {mutex,cond,0,0};
pthread_t thread1_id,thread2_id;
if(pthread_create(&thread1_id,NULL,compare_v,(void*)&data) != 0)
{
printf("create thread1 error\n");
return 1;
}
if(pthread_create(&thread2_id,NULL,get_v,(void*)&data) != 0)
{
printf("create thread2 error\n");
return 1;
}
pthread_join(thread1_id,NULL);
pthread_join(thread2_id,NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
// 获得输入和输出结果的线程函数
void* get_v(void* data)
{
Data* d = (Data*)data;
// 锁定数据,避免多个线程同时操作
pthread_mutex_lock(&d->mutex);
// 读取输入
scanf("%d",&d->res);
// 设置判断条件变量,使计算线程知道已经读取数据完毕
d->is_wait = 1;
// 等待计算线程计算完成
pthread_cond_wait(&d->cond,&d->mutex);
// pthread_cond_wait函数最后会将mutex再次锁住,所以这里要解锁
pthread_mutex_unlock(&d->mutex);
printf("res = %d",d->res);
return (void*)NULL;
}
// 负责计算的线程函数
void* compare_v(void*data)
{
Data* d = (Data*)data;
// 先锁住,避免数据同时有多个线程操作
pthread_mutex_lock(&d->mutex);
// 判断输入线程是否已经正确读取输入
while(d->is_wait == 0)
{
// 先解除锁定,给读取输入的线程机会去读取输入
pthread_mutex_unlock(&d->mutex);
usleep(0.1);
// 再次锁定
pthread_mutex_lock(&d->mutex);
}
// 读取数据线程成功读取,等待计算结果,下面进行计算
int res = 0;
for(int i = 0;i <= d->res;i++)
{
res += i;
}
d->res = res;
// 先解除上面的锁定
pthread_mutex_unlock(&d->mutex);
// 通知输出线程输出数据
pthread_cond_broadcast(&d->cond);
return (void*)NULL;
}
- 读者和写者
typedef struct
{
int data;
int write;/* 0表示不能写数据,1表示可以写数据 */
int read;/* 0表示不能读数据,1表示可以读数据 */
int length;
pthread_cond_t write_cond;
pthread_cond_t read_cond;
pthread_mutex_t write_mutex;
pthread_mutex_t read_mutex;
}Data;
int main(void)
{
Data data = {0,0,1,3};
pthread_cond_init(&data.write_cond,NULL);
pthread_cond_init(&data.read_cond,NULL);
pthread_mutex_init(&data.write_mutex,NULL);
pthread_mutex_init(&data.read_mutex,NULL);
pthread_t thread1_id,thread2_id;
pthread_create(&thread1_id,NULL,writer,(void*)&data);
pthread_create(&thread2_id,NULL,reader,(void*)&data);
pthread_join(thread1_id,NULL);
pthread_join(thread2_id,NULL);
return 0;
}
/* 得到reader方法通知写数据,再通知writer方法写数据,并阻塞等待reader方法读数据 */
void* writer(void* data)
{
Data* d = (Data*)data;
for(int i = 0;i < d->length;i++)
{
pthread_mutex_lock(&d->write_mutex);
while(d->write == 0)
{
pthread_mutex_unlock(&d->write_mutex);
usleep(0.1);
pthread_mutex_lock(&d->write_mutex);
}
printf("write %d\n",d->data);
d->write = 0;
d->read = 1;
pthread_mutex_unlock(&d->write_mutex);
pthread_cond_broadcast(&d->read_cond);
if(i < ( d->length - 1) )
{
pthread_cond_wait(&d->write_cond,&d->write_mutex);
pthread_mutex_unlock(&d->write_mutex);
}
}
return (void*)NULL;
}
/* 读数据方法,先读数据,再通知writer方法写一个数据,并阻塞等write方法通知 */
void* reader(void* data)
{
Data* d = (Data*)data;
for(int i = 0;i < d->length;i++)
{
pthread_mutex_lock(&d->write_mutex);
d->data = i;
d->write = 1;
printf("read %d\n",d->data);
pthread_cond_broadcast(&d->write_cond);
while(d->read == 0)
{
pthread_mutex_unlock(&d->write_mutex);
usleep(0.1);
pthread_mutex_lock(&d->write_mutex);
}
d->read = 0;
pthread_mutex_unlock(&d->write_mutex);
if(i < ( d->length - 1) )
{
pthread_cond_wait(&d->read_cond,&d->read_mutex);
pthread_mutex_unlock(&d->read_mutex);
}
}
return (void*)NULL;
}
线程的同步和互斥-线程信号量
- 信号量从本质上是一个非负整数计数器,是共享资源的数目,通常被用来控制对共享资源的访问。
- 信号量可以实现线程的同步和互斥
- 通过sem_post()和sem_wait()函数对信号量进行加减操作从而解决线程的同步与互斥
信号量数据类型
- sem_t
信号量的创建和销毁
int sem_init(sem_t* sem,int pshared,unsigned value);
int sem_destroy(sem_t* sem);
- 返回:成功返回0,失败返回错误编号
- 头文件
#include <semaphore.h>
参数
sem
- 信号量指针
pshared
- 是否在进程间共享的标志,0为不共享,1为共享
value
- 信号量的初始值
信号量的加和减操作
int sem_post(sem_t* sem);
- 功能:增加信号量的值
int sem_wait(sem_t* sem);
- 功能:减少信号量的值
int sem_trywait(sem_t* sem);
- 功能:sem_wait的非阻塞版本
- 返回:成功返回0,出错返回错误编号
- 调用sem_post一次信号作加1操作
- 调用sem_wait一次信号量作减一操作
- 当线程调用sem_wait后,若信号量的值小于0则线程阻塞。只有在其他线程在调用sem_post对信号量作加操作之后并且其值大于或等于0时,阻塞的线程才能继续运行
- 示例
int main(void)
{
// 按照 c->b->a 的顺序执行
sem_init(&sem1,0,0);
sem_init(&sem2,0,0);
pthread_t thread_a,thread_b,thread_c;
pthread_create(&thread_a,NULL,a,(void*)NULL);
pthread_create(&thread_b,NULL,b,(void*)NULL);
pthread_create(&thread_c,NULL,c,(void*)NULL);
pthread_join(thread_a,NULL);
pthread_join(thread_b,NULL);
pthread_join(thread_c,NULL);
return 0;
}
void* a(void* data)
{
sem_wait(&sem1);
printf("func a\n");
return (void*)NULL;
}
void* b(void* data)
{
sem_wait(&sem2);
sem_post(&sem1);
printf("func b\n");
return (void*)NULL;
}
void* c(void* data)
{
sem_post(&sem2);
printf("func c\n");
return (void*)NULL;
}
- PV操作-银行账户
ypedef struct
{
int deposit;
sem_t sem;
}Account;
int main(void)
{
pthread_t thread1_id,thread2_id;
Account account = {888};
sem_init(&account.sem,0,1);
pthread_create(&thread1_id,NULL,func1,(void*)&account);
pthread_create(&thread2_id,NULL,func2,(void*)&account);
pthread_join(thread1_id,NULL);
pthread_join(thread2_id,NULL);
return 0;
}
void* func1(void* data)
{
Account* a = (Account*)data;
// P(1)
sem_wait(&a->sem);
if(a->deposit >= 888)
{
a->deposit -= 888;
}
// V(1)
sleep(2);
printf("func1; deposit = %d\n",a->deposit);
sem_post(&a->sem);
return (void*)NULL;
}
void* func2(void* data)
{
Account* a = (Account*)data;
// P(1)
sem_wait(&a->sem);
if(a->deposit >= 888)
{
a->deposit -= 888;
}
// V(1)
sleep(1);
printf("func2; deposit = %d\n",a->deposit);
sem_post(&a->sem);
return (void*)NULL;
}
- 计算
typedef struct
{
int data;
sem_t get_sem,c_sem;
}Data;
int main(void)
{
Data data = {1};
sem_init(&data.get_sem,0,0);
sem_init(&data.c_sem,0,0);
pthread_t thread1_id,thread2_id;
// get_v线程先读取输入,calculate_v线程计算,再由get_v线程输出
pthread_create(&thread1_id,NULL,calculate_v,(void*)&data);
pthread_create(&thread2_id,NULL,get_v,(void*)&data);
pthread_join(thread1_id,NULL);
pthread_join(thread2_id,NULL);
return 0;
}
void* get_v(void* data)
{
Data* d = (Data*)data;
scanf("%d",&d->data);
sem_post(&d->c_sem);
sem_wait(&d->get_sem);
printf("data = %d\n",d->data);
return (void*)NULL;
}
void* calculate_v(void* data)
{
Data* d = (Data*)data;
sem_wait(&d->c_sem);
d->data += 8;
sem_post(&d->get_sem);
return (void*)NULL;
}
死锁
- 两个线程试图同时占用两个资源,并按不同次序锁定相应的共享资源
解决方式
- 按相同的次序锁定相应的共享资源
- 使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞函数
线程和信号
- 进程中每个线程都有自己的信号屏蔽字和信号未决字
- 信号的处理方式是进程中所有线程共享的
- 进程中的信号是传递到单个线程的
定时器是进程资源,进程中所有线程共享相同的定时器
- 子线程调用alarm函数产生的alarm信号发送给主控线程
int pthread_sigmask(int how,const sigset_t*restrict set,sigset_t*restrict oset);
- 子线程调用alarm函数产生的alarm信号发送给主控线程
- 功能:线程的信号屏蔽
- 返回:成功返回0,失败返回错误编号
- 示例
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
void* func1(void*);
void* func2(void*);
void alarm_handle(int);
int main(void)
{
pthread_t t1_id,t2_id;
pthread_create(&t1_id,NULL,func1,(void*)NULL);
pthread_create(&t2_id,NULL,func2,(void*)&t1_id);
// 虽然是睡眠10s,但是子线程会发送alarm信号(SIGALRM默认给主线程处理),这时主线程会醒来继
续运行,可以在主线程中屏蔽,如下
// 编译时加上 std=gnu99,不然会显示sigset_t类型没有定义
sigset_t sig_set;
sigemptyset(&sig_set);
sigaddset(&sig_set,SIGALRM);
// 屏蔽,当前线程不会处理
pthread_sigmask(SIG_SETMASK,&sig_set,NULL);
for(int i = 0;i < 10;i++)
{
printf("master thread i = %d;thread_id = %d\n",i,pthread_self());
sleep(10);
}
pthread_join(t1_id,NULL);
pthread_join(t2_id,NULL);
return 0;
}
void alarm_handle(int sig)
{
printf("I am alarm handle;threadid = %d\n",pthread_self());
signal(SIGALRM,alarm_handle);
alarm(2);
}
void* func1(void* data)
{
// 在子线程中设置信号处理函数
signal(SIGALRM,alarm_handle);
alarm(2);
for(int i = 0;i < 100;i++)
{
printf("thread1 i = %d;thread_id = %d\n",i,pthread_self());
sleep(1);
}
return (void*)NULL;
}
void* func2(void* data)
{
for(int i = 0;i < 100;i++)
{
if(i == 8)
{
// 终止func1进程
pthread_cancel(*((pthread_t*)data));
}
printf("thread2 i = %d;thread_id = %d\n",i,pthread_self());
sleep(1);
}
return (void*)NULL;
}