什么是线程?
在一个程序里的一个执行路线就叫做线程,线程是进程的一个实体,是CPU调度和分配的基本单位,它是比进程更小的可以独立运行的基本单位。一切进程至少都有一个线程。
线程与进程
进程是资源竞争的基本单位
线程是程序执行的最小单位
一个线程可以与所属进程内其他线程的共享进程资源
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器,栈,线程ID,errno,信号屏蔽字,调度优先级)
线程相关函数
功能:创建一个线程
原型:int pthread_create(pthread_t *thread,const pthread_attr_t *attr,
void*(*start_routine)(void*),void* arg)
参数:
thread:返回线程ID
attr:设置线程属性,为NULL时表示默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:
成功返回0,失败返回错误码
线程终止
如果需要终止某个线程而不终止整个进程可以有三种方法
1 从线程函数return,但对主线程不适用,从main函数return相当于调用exit。
2 线程可以调用函数pthrea_exit终结自己。
3 一个线程可以调用pthread_cancel函数来终止同一进程中的其他线程。
线程终止函数
原型:void pthread_exit(void* value_ptr)
参数:
value_ptr:不要指向一个局部变量
返回值:
无返回值,线程结束时无法返回到它的调用者
原型:int pthread_cancel(pthread_t thread);
功能:取消一个执行中的进程
参数:
thread:线程ID
返回值:
成功返回0,失败返回错误码
注意:需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全
局 的或者是⽤malloc分配的,不能在线程函数的栈上分配
线程等待
为啥子需要进程等待
1 线程退出后,其空间地址没有释放,仍然在进程的地址空间内
创建的新线程不会使用刚才退出线程的地址空间(类似与僵尸进程,占用的资源
在进程结束前都不会被回收,所以一个线程结束后我们应该等待回收其资源)
2 可以防止新创建的线程还未结束,整个进程就结束退出
功能:等待线程结束
原型:int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread:线程ID
value_ptr:它指向⼀个指针,后者指向线程的返回值
返回值:
成功返回0;失败返回错误码
例:用两个线程来对全局变量进行加减
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
//pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
int num=0;
void *add(void*arg){
int i=0;
int tmp;
for(;i<500;i++){
//pthread_mutex_lock(&mutex);
tmp=num+1;
num=tmp;
printf("add+1 result is:%d\n",num);
//pthread_mutex_unlock(&mutex);
}
return ((void *)0);
// return 0;
}//线程执行函数加500次操作
void * sub(void* arg){
int i=0;
int tmp;
for(;i<500;i++){
//pthread_mutex_lock(&mutex);
tmp=num-1;
num=tmp;
printf("sub-1 result is:%d\n",num);
// pthread_mutex_unlock(&mutex);
}
return ((void *)0);
// return 0;
}
//线程执行函数减500次操作
int main(int argc, char** argv){
pthread_t tid1,tid2;
void* ret;
int err;
if(pthread_create(&tid1,NULL,add,NULL)!=0){
printf("create error\n");
exit(-1);
}
if(pthread_create(&tid2,NULL,sub,NULL)!=0){
printf("create errr\n");
exit(-1);
}
if((err=pthread_join(tid1,&ret))!=0){
printf("join error:%s\n",strerror(err));
exit(-1);
}
printf("thread 1 exit code %d\n",(int)ret);
if((err=pthread_join(tid2,&ret))!=0){
printf("join error:%s\n",strerror(err));
exit(-1);
}
printf("thread 2 exit code %d\n",(int)ret);
return 0;
}
注意:如果编译时报错undefined reference to `pthread_create’
原因是:pthread库不是linux默认的库,所以在编译时候需要指明libpthread.a库。
解决方法:在编译时,加上-lpthread参数。
运行后我们发现了一点不和谐的东东,这是由于多个线程并发操作共享变量带来的问题。为了解决多线程的访问冲突问题我们可以引入互斥锁机制。获得锁的线程可以完成读写修改操作,然后释放锁给其他线程,没有获得锁的线程无法对共享数据进行操作,这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。
线程同步与互斥
互斥量的初始化
静态分配:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL
互斥锁的销毁
int pthread_mutex_destory(pthread_mutex*mutex);
销毁互斥量需要注意:
使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁
互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
注意:发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程
同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用
会陷入阻塞,等待互斥量解锁。
利用互斥量修改上述程序后
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
int num=0;
void *add(void*arg){
int i=0;
int tmp;
for(;i<500;i++){
pthread_mutex_lock(&mutex);
tmp=num+1;
num=tmp;
printf("add+1 result is:%d\n",num);
pthread_mutex_unlock(&mutex);
}
return ((void *)0);
// return 0;
}//线程执行函数加500次操作
void * sub(void* arg){
int i=0;
int tmp;
for(;i<500;i++){
pthread_mutex_lock(&mutex);
tmp=num-1;
num=tmp;
printf("sub-1 result is:%d\n",num);
pthread_mutex_unlock(&mutex);
}
return ((void *)0);
// return 0;
}
条件变量
初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
销毁
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量
唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_signal唤醒等待该条件的某个线程,pthread_cond_broadcast唤醒等待该条件的所有线程。
实现按一定次序启动线程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
int num=0;
void* pthread_func(void* arg){
int i=(int)arg;
int ret;
pthread_mutex_lock(&mutex);
while(i!=num){
//if(i!=num){
printf("thread %d wait\n",i);
ret=pthread_cond_wait(&cond,&mutex);
if(ret==0)
{
printf("thread %d wait success\n",i);
}else {
printf("thread %d wait fail\n",i);
}
}
printf("thread %d is runing\n",i);
num++;
pthread_mutex_unlock(&mutex);
pthread_cond_broadcast(&cond);
return 0;
}
int main()
{
int i=0;
int err;
pthread_t tid[4];
void *tret;
for(;i<4;i++){
if(err=pthread_create(&tid[i],NULL,pthread_func,(void*)i)!=0){
printf("pthread_creadte error:%s\n",strerror(err));
exit(-1);
}
}
for(i=0;i<4;i++){
if(err=pthread_join(tid[i],&tret)!=0){
printf("pthread_join error %d%s\n",i,strerror(err));
exit(-1);
}
}
return 0;
}
运行结果:
为什么pthread_cond_wait需要互斥量?
设想一下如果只有一个线程等待,由于条件不满足,所以它就会一直等,
一直等肯定不是个事,所以我们需要一个线程来改变原来共享变量的条
件,使条件得到满足,并且通知原来等待在条件变量上的线程
想要修改条件就必须修改共享数据,所以就必须要用互斥锁来保护,没
有互斥锁就无法安全的获取和修改共享数据。