多线程
线程介绍
每个进程都会有一个主线程,在创建进程时创建,往后创建的线程都属于子线程;线程在进程里不断抢占运行时间片;当进程遇到return 结束,所有的线程全部结束。
线程分类
线程主要分为用户级线程和内核级线程
用户级线程主要解决上下文切换问题,其调度由用户控制
内核级线程由内核调度机制实现
当CPU分配给线程的时间片用完后,线程还未执行完毕,线程会从运行态转为阻塞态,将CPU让给其他线程使用。
线程的一些实现
在Linux,线程使用pthread线程库,编译时要加上 -lpthread
每个线程都有自己的线程标识id,pthread_t是线程的数据类型
线程创建
关于线程属性,一般在网络编程上,设置为分离属性,结束后线程自动释放。
!在64位操作系统上,创建线程,当线程进入执行函数后,传递的参数void* arg中,大小是8字节,与4字节int不匹配
线程终止
pthread_exit((void*)0);线程自动退出,同时返回0
在线程执行函数中使用return,也是线程自动退出
pthread_cancel(tid),tid线程被动终止
pthread_join(th,void**return);//等待th线程退出,同时接收其返回值。同时该函数是阻塞的,会一直等待,直到线程结束释放
pthread_join获得pthread_exit()返回的参数
二级指针把获得的值只进行一次解引用赋值,最后,result变量存的值就是线程返回的值
线程清理函数
线程与进程
线程状态
线程属性
线程属性要初始化,用完后要删除
设置线程属性及获取线程属性
#include<pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t* attr,int * detachstate);//获取当前线程属性的分离值
int pthread_attr_setdetachstate(const pthread_attr_t* attr,int detachstate);
成功返回0,出错返回出错错误编号
detachstate取值:
PTHREAD_CREATE_JOINABLE(默认) 正常启动线程
PTHREAD_CREATE_DETACHED 以分离状态启动线程
以分离状态启动的线程,不需要pthread_join()阻塞等待子线程结束释放资源,子线程结束后会自动释放资源
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
void outstate(pthread_attr_t *attr)//获取线程属性的分离值
{
int state;//存储分离值
if(pthread_attr_getdetachstate(attr,&state)!=0)//通过函数获取
{
perror("getdetachstat error");
}else
{
if(state==PTHREAD_CREATE_JOINABLE) puts("默认");
else if(state==PTHREAD_CREATE_DETACHED) puts("分离");
else puts("获取失败");
}
}
void* th_fun(void* arg)//线程执行函数
{
long sum=0;
for(int i=0;i<100;i++)
sum+=i;
return (void*)sum;
}
int main()
{
pthread_t default_th,detach_th;//定义线程
pthread_attr_t attr;//声明线程属性
pthread_attr_init(&attr);//初始化线程属性
outstate(&attr);//获取线程分离值
int err;
//以线程属性默认值,正常启动线程
if((err=pthread_create(&default_th,&attr,th_fun,(void*)0))!=0)
{
perror("启动失败");
}
int res;
if((err=pthread_join(default_th,(void*)&res))!=0)
{
perror("阻塞失败");
}else
{
printf("res%d\n",(int)res);
}
puts("-------------------------------------------------------------");
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置线程属性的分离状态为分离
outstate(&attr);
if((err=pthread_create(&detach_th,&attr,th_fun,(void*)0))!=0)
{
perror("启动失败");
}
if((err=pthread_join(detach_th,(void*)&res))!=0)
{
perror("阻塞失败");
}else
{
printf("res%d\n",(int)res);
}
puts("-------------------------------------------------------------");
pthread_attr_destroy(&attr);//最后要销毁线程属性
printf("%lx\n",pthread_self());
return 0;
}
线程互斥和同步
线程同步主要有同步变量和线程信号量
线程互斥主要有互斥锁,读写锁,线程信号量
assert(int experence) 如果括号里为true则不执行任何;如果括号里为false则显示信息并终止程序
线程互斥
互斥锁
主要通过互斥锁实现pthread_mutex_t
互斥锁属性
设置互斥锁类型的实例:
互斥锁创建的一些步骤
互斥锁:
定义互斥锁的属性
初始化互斥锁属性
可以更改互斥锁的某些属性
定义互斥锁
初始化互斥锁,函数参数传入互斥锁,互斥锁的属性结构体
加锁、解锁
删除互斥锁的属性结构体
删除互斥锁
读写锁
对于互斥锁,对于临界资源的访问,都要加锁,缺乏读并发性,对于一些不会修改临界资源的线程,可以不用加互斥锁,考虑加读锁,多个线程对资源的访问加读锁不互斥
pthread_rwlock_t 都是读锁,不会发生互斥
读、读 不阻塞
读、写 阻塞
写、读 阻塞
写、写 阻塞
线程同步——条件变量
对于互斥锁,只要两种状态:锁定和非锁定
线程进入条件变量等待,要加互斥锁,条件变量是共享资源
条件变量的pthread_cond_wait()函数
条件满足后通知
线程同步的例子
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<time.h>
typedef struct//两个线程共享的结构体资源
{
int res;
int is_wait;//用户给出用于判断的条件,判断线程是否去占用资源
pthread_cond_t cond;//条件变量
pthread_mutex_t mutex;//互斥锁,限制条件变量
}Result;
void* set_fn(void* arg)//计算
{
int i=1,sum=0;
for(;i<=100;i++)
sum+=i;//将计算结果放到共享结构体
Result* r=(Result*)arg;
r->res=sum;
pthread_mutex_lock(&r->mutex);//对两个线程共享的判断条件加锁,每次只能一个线程访问
while(!r->is_wait)//得到结果的线程没准备好
{
pthread_mutex_unlock(&r->mutex);//释放让另一线程准备
usleep(100);
pthread_mutex_lock(&r->mutex);//加锁判断
}
pthread_mutex_unlock(&r->mutex);
//已经准备好了
//唤醒等待结果的线程
pthread_cond_broadcast(&r->cond);
return (void*)0;
}
void* get_fn(void* arg)//获取结果
{
Result* r=(Result*)arg;
pthread_mutex_lock(&r->mutex);//加锁修改准备
r->is_wait=1;//已准备好,进入等待队列
//准备好后,就进入等待,然后就释放锁,让set_fn函数能获得锁,接着又加锁,进入队列,释放锁,wait等待阻塞
pthread_cond_wait(&r->cond,&r->mutex);
pthread_mutex_unlock(&r->mutex);
int res=r->res;
printf("结果%d\n",res);
return (void*)0;
}
int main()
{
int err;
pthread_t cal,get;
Result r;//定义共享资源
r.is_wait=0;
pthread_cond_init(&r.cond,NULL);//初始化共享资源里的锁,条件变量
pthread_mutex_init(&r.mutex,NULL);
if((err=pthread_create(&cal,NULL,set_fn,(void*)&r))!=0)//启动计算线程,进入set_fn函数,参数为共享结构体
{
perror("pthread set error");
}
if((err=pthread_create(&get,NULL,get_fn,(void*)&r))!=0)//启动结果线程,进入get_fn函数,参数为共享结构体
{
perror("pthread get error");
}
pthread_join(cal,NULL);//阻塞
pthread_join(get,NULL);
pthread_cond_destroy(&r.cond);//销毁
pthread_mutex_destroy(&r.mutex);
return 0;
}
读者写者(写者写后通知读者,读者读后通知写者)
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
typedef struct//共享资源
{
int value;
pthread_cond_t rc;//读者条件变量
pthread_mutex_t rm;//读者锁
int r_wait;//判断读者是否去占用共享资源读
pthread_cond_t wc;//写者条件变量
pthread_mutex_t wm;//写者锁
int w_wait;//判断写者是否占用共享资源去写
}storage;
void set_data(storage* s,int value)
{
s->value=value;
}
int get_data(storage*s)
{
return s->value;
}
void* set_fn(void* arg)//写者:判断读者是否准备好读,唤醒,读完后写者准备好写,等待读者读完唤醒写
{
storage* s=(storage*)arg;
int i=1;
for(;i<=10;i++)
{
set_data(s,i);//写入
printf("0x%lx write data:%d\n",pthread_self(),i);
pthread_mutex_lock(&s->rm);//加读者锁判断读者是否准备好读
while(!s->r_wait)
{
pthread_mutex_unlock(&s->rm);
usleep(100);
pthread_mutex_lock(&s->rm);
}
s->r_wait=0;//读者读后置为0
pthread_mutex_unlock(&s->rm);
//唤醒读者读,读者在写者等待队列里
pthread_cond_broadcast(&s->wc);
//加锁准备好写
pthread_mutex_lock(&s->wm);
s->w_wait=1;
pthread_cond_wait(&s->rc,&s->wm);//等待读者唤醒写,写者进入读者等待队列
//释放写者锁?
pthread_mutex_unlock(&s->wm);
}
return (void*)0;
}
void* get_fn(void* arg)//读者:准备好读,wait等待写者唤醒读,判断写者是否准备好写,唤醒
{
storage* s=(storage*)arg;
int i=1;
for(;i<=10;i++)
{
pthread_mutex_lock(&s->rm);//加读者锁,准备
s->r_wait=1;
pthread_cond_wait(&s->wc,&s->rm);//读者等待,放到写者等待队列
pthread_mutex_unlock(&s->rm);
printf("0x%lx readyer data:%d\n",pthread_self(),get_data(s));//读取数据
pthread_mutex_lock(&s->wm);//加写者锁判断写者是否准备好写
while(!s->w_wait)
{
pthread_mutex_unlock(&s->wm);
usleep(100);
pthread_mutex_lock(&s->wm);
}
s->w_wait=0;//写者写后置为0
pthread_mutex_unlock(&s->wm);
//唤醒写者写,写者在读者的等待队列,等待读者读完唤醒
pthread_cond_broadcast(&s->rc);
}
return (void*)0;
}
int main()
{
int err;
pthread_t rth,wth;
storage s;//定义共享资源
//初始化条件变量,锁
pthread_cond_init(&s.rc,NULL);
pthread_cond_init(&s.wc,NULL);
pthread_mutex_init(&s.rm,NULL);
pthread_mutex_init(&s.wm,NULL);
s.r_wait=0;
s.w_wait=0;
if((err=pthread_create(&rth,NULL,set_fn,(void*)&s))!=0)//读者线程
perror("ready thread faile");
if((err=pthread_create(&wth,NULL,get_fn,(void*)&s))!=0)//写者线程
perror("writh thread faile");
pthread_join(rth,NULL);//阻塞
pthread_join(wth,NULL);
pthread_cond_destroy(&s.rc);//销毁
pthread_cond_destroy(&s.wc);
pthread_mutex_destroy(&s.rm);
pthread_mutex_destroy(&s.wm);
return 0;
}
线程信号量
信号量的一些操作:c先运行,c运行后释放sem2,让b运行,b运行释放sem1,让a运行
#include<stdio.h>
#include<semaphore.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<time.h>
//定义线程信号量
sem_t sem1;
sem_t sem2;
void* a_fn(void* arg)//线程执行函数
{
sem_wait(&sem1);//调用阻塞,等待释放
puts("thread_a sem1 running");
return (void*)0;
}
void* b_fn(void* arg)
{
sem_wait(&sem2);//同样调用阻塞,等待阻塞
puts("thread_b sem2 running");
sem_post(&sem1);//sem1++;让sem1=0继续运行
return (void*)0;
}
void* c_fn(void* arg)
{
puts("thread_c running");
sem_post(&sem2);//释放sem2信号量,sem2++,让b线程运行
return (void*)0;
}
int main()
{
pthread_t a,b,c;
sem_init(&sem1,0,0);//信号量初始化
sem_init(&sem2,0,0);
pthread_create(&a,NULL,a_fn,(void*)0);//启动线程
pthread_create(&b,NULL,b_fn,(void*)0);
pthread_create(&c,NULL,c_fn,(void*)0);
pthread_join(a,NULL);//阻塞
pthread_join(b,NULL);
pthread_join(c,NULL);
sem_destroy(&sem1);//销毁
sem_destroy(&sem2);
return 0;
}
信号量初值为1,一些合理pv操作可使信号量实现互斥功能
信号量初值为0,一些合理pv操作可使信号量实现同步功能
死锁
死锁主要是两个线程试图同时占用两个资源,并按不同次序锁定相应共享资源
解决:按相同次序锁定相应共享资源;用pthread_mutex_trylock()加锁,是pthread_mutex_lock的非阻塞版
线程信号
sigset_t set;//信号屏蔽
sigemptyset(&set);//清空
sigaddset(&set,SIGALRM);
pthread_sigmask(SIG_SETMASK,&set,NULL);//对主控线程屏蔽SIGALRM信号
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<time.h>
void sig_handler(int sign)
{
printf("pthread signal id %lx\n",pthread_self());
if(sign==SIGALRM)
puts("timeout...");
alarm(3);
}
void* th_fn(void* arg)
{
if(signal(SIGALRM,sig_handler)==SIG_ERR)
{
perror("signal sigalrm error");
}
//在子线程中设置定时器
alarm(3);
int i=1;
for(;i<=30;i++)
{
printf("pthread %lx i:%d\n",pthread_self(),i);
sleep(1);
}
return (void*)0;
}
void* th_fn2(void* arg)//子线程2运行函数
{
pthread_t th1=(pthread_t)arg;//强转,获得子线程1
int i;
for(i=1;i<=10;i++)
{
if(i==5)
{
pthread_cancel(th1);//在子线程2中通过子线程1,终止子线程1运行
alarm(0);//取消定时器
}
printf("pthread2 cancel:%lx i:%d\n",pthread_self(),i);
sleep(1);
}
}
int main()
{
pthread_t th,th2;
pthread_attr_t attr;//定义线程属性
pthread_attr_init(&attr);//初始化线程属性
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//以分离状态启动线程
int err;
if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0)
perror("thread create error");
if((err=pthread_create(&th2,&attr,th_fn2,(void*)th))!=0)//用子线程2终止子线程1,传入的是子线程1的数据th
perror("thread create error");
sigset_t set;//信号屏蔽
sigemptyset(&set);//清空
sigaddset(&set,SIGALRM);
pthread_sigmask(SIG_SETMASK,&set,NULL);//对主控线程屏蔽SIGALRM信号
while(1)
{
printf("control pthread %lx running..\n",pthread_self());
sleep(10);
}
puts("control pthread over");
return 0;
}
gcc编译
链接头文件的编译