一.多线程
1.了解多线程
解决多任务实现。
历史上Unix服务器不支持多线程
Unix/Linux上实现多线程有两种方式:
内核支持多线程
使用进程的编程技巧封装进程实现多线程:轻量级多线程
多线程的库:
libpthread.so -lpthread
pthread.h
2.创建多线程
2.1.代码?
回调函数
2.2.线程ID?
pthread_t
2.3.运行线程?
pthread_create
int pthread_create(
pthread_t *th,//返回进程ID
const pthread_attr_t *attr,//线程属性,为NULL/0,使用进程的默认属性
void*(*run)(void*),//线程代码
void *data);//传递线程代码的数据
看一个小例子:
#include <stdio.h>
#include <pthread.h>
void *run(void *data)
{
printf("我是线程!\n");
}
main()
{
pthread_t tid;
pthread_create(&tid, 0, run, 0);
}
运行程序,发现并没有输出。为什么呢?请看结论: 结论: 1.程序结束所有子线程就结束 解决办法:等待子线程结束 sleep/pause int pthread_join( pthread_t tid,//等待子线程结束 void **re);//子线程结束的返回值
#include <stdio.h>
#include <pthread.h>
void *run(void *data)
{
printf("我是线程!\n");
}
main()
{
pthread_t tid;
pthread_create(&tid, 0, run, 0);
//sleep(1);
pthread_join(tid, (void **)0);
}
2.创建子线程后,主线程继续完成系统分配时间片。 3.子线程结束就是线程函数返回。 4.子线程与主线程有同等优先级别.作业: 写一个程序创建两个子线程
#include <stdio.h>
#include <pthread.h>
void *run(void *data)
{
printf("我是线程!\n");
}
void *run2(void *data)
{
printf("我是线程2!\n");
}
main()
{
pthread_t tid;
pthread_t tid2;
pthread_create(&tid, 0, run, 0);
pthread_create(&tid2, 0, run2, 0);
//sleep(1);
pthread_join(tid, (void **)0);
pthread_join(tid2, (void**)0);
}
3.线程的基本控制 线程的状态: ready->runny->deady | sleep/pause 结束线程? 内部自动结束:(建议) return 返回值;(在线程函数中使用) void pthread_exit(void*);(在任何线程代码中使用)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
void call() //用一个函数来退出线程的时候,就可以看到return和exit的区别
{
pthread_exit("Kill"); //可以让线程结束
return ; //只能让函数退出,并不能让线程退出
}
void* run(void* data)
{
while(1)
{
printf("我是线程!%s\n");
sched_yield(); //放弃当前时间片。等待下一次执行
//return "hello";
pthread_exit("world");
}
}
main()
{
pthread_t tid;
char *re;
pthread_create(&tid,0,run,0);
pthread_join(tid,(void**)&re); //re接收返回值
printf("%s\n",re);
}
外部结束一个线程. pthread_cancel(pthread_t);小应用: 用多线程来写7为随机数和当前时间的显示
#include <curses.h>
#include <pthread.h>
#include <time.h>
#include <math.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
//全局变量两个窗体
WINDOW *wtime,*wnumb;
pthread_t thnumb,thtime;
pthread_mutex_t m;
//线程1:随机数
void*runnumb(void *d)
{
int num;
while(1)
{
//循环产生7位随机数
num=rand()%10000000;
pthread_mutex_lock(&m);
//显示
mvwprintw(wnumb,1,2,"%07d",num);
//刷新
refresh();
wrefresh(wnumb);
pthread_mutex_unlock(&m);
usleep(1);
}
return 0;
}
//线程2:时间
void*runtime(void*d)
{
time_t tt;
struct tm *t;
while(1)
{
//循环取时间
tt=time(0);
t=localtime(&tt);
pthread_mutex_lock(&m);
//显示
mvwprintw(wtime,1,1,"%02d:%02d:%02d",
t->tm_hour,t->tm_min,t->tm_sec);
//刷新
refresh();
wrefresh(wtime);
pthread_mutex_unlock(&m);
usleep(1);
}
}
main()
{
//初始化curses
initscr();
curs_set(0);
noecho();
keypad(stdscr,TRUE);
wnumb=derwin(stdscr,3,11,
(LINES-3)/2,(COLS-11)/2);
wtime=derwin(stdscr,3,10,0,COLS-10);
box(wnumb,0,0);
box(wtime,0,0);
refresh();
wrefresh(wnumb);
wrefresh(wtime);
pthread_mutex_init(&m,0);//2
//创建线程1
pthread_create(&thnumb,0,runnumb,0);
//创建线程2
pthread_create(&thtime,0,runtime,0);
//等待按键
//结束
getch();
pthread_mutex_destroy(&m);//3
delwin(wnumb);
delwin(wtime);
endwin();
}
4.多线程的问题 数据脏
#include <stdio.h>
#include <pthread.h>
int a=0,b=0;
void display()
{
a++;
b++;
if(a!=b)
{
printf("%d!=%d\n",a,b);
a=b=0;
}
}
void *r1()
{
while(1)
{
display();
}
}
void *r2()
{
while(1)
{
display();
}
}
main()
{
pthread_t t1,t2;
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
}
看上去好像程序没有问题,但是会输出很多很多不相等的a,b数据,甚至很多a,b相等的也被输出来了。这就是两个线程之前共用数据时的问题。 5.多线程问题的解决 互斥锁/互斥量 mutex 1.定义互斥量pthread_mutex_t 2.初始化互斥量 默认是1 pthread_mutex_init 3.互斥量操作 置0 phtread_mutex_lock 判定互斥量0:阻塞 1:置0,返回 置1 pthread_mutex_unlock 置1返回 强烈要求成对使用 4.释放互斥量pthread_mutex_destroy
#include <stdio.h>
#include <pthread.h>
//1.定义互斥量
pthread_mutex_t m;
int a=0,b=0;
void display()
{
//3.操作互斥量
pthread_mutex_lock(&m);
a++;
b++;
if(a!=b)
{
printf("%d!=%d\n",a,b);
a=b=0;
}
pthread_mutex_unlock(&m);
}
void *r1()
{
while(1)
{
display();
}
}
void *r2()
{
while(1)
{
display();
}
}
main()
{
pthread_t t1,t2;
//2. 初始化互斥量
pthread_mutex_init(&m,0);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
//4. 释放互斥量
pthread_mutex_destroy(&m);
}
结论: 互斥量保证锁定的代码一个线程执行, 但不能保证必需执行完! 5.在lock与unlock之间,调用pthread_exit? 或者在线程外部调用pthread_cancel? 其他线程被永久死锁. 6.pthread_cleanup_push { pthread_cleanup_pop } 这对函数作用类似于atexit 注意: 这不是函数,而是宏. 必须成对使用
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t m;
void handle(void *d)
{
printf("退出后的调用!\n");
pthread_mutex_unlock(&m);
}
void* runodd(void *d)
{
int i=0;
for(i=1;;i+=2)
{
pthread_cleanup_push(handle,0); //pthread_exit()和pthread_cancle()以及pthread_cleanup_pop(1)参数为1时会触发
pthread_mutex_lock(&m);
printf("%d\n",i);
pthread_cleanup_pop(1); //参数为1就会弹出handle并执行函数,如果参数为0,则只弹出,不执行函数。
}
}
void* runeven(void *d)
{
int i=0;
for(i=0;;i+=2)
{
pthread_cleanup_push(handle,0);
pthread_mutex_lock(&m);
printf("%d\n",i);
pthread_cleanup_pop(1);
}
}
main()
{
pthread_t todd,teven;
pthread_mutex_init(&m,0);
pthread_create(&todd,0,runodd,0);
pthread_create(&teven,0,runeven,0);
sleep(5);
pthread_cancel(todd);
pthread_join(todd,(void**)0);
pthread_join(teven,(void**)0);
pthread_mutex_destroy(&m);
}
6.多线程的应用
二.多线程同步
互斥量/信号/条件量/信号量/读写锁
1.sleep与信号
pthread_kill向指定线程发送信号
signal注册的是进程的信号处理函数.
pthread_kill+sigwait控制进程
1.1.定义信号集合
1.2.初始化信号集合
1.3.等待信号
1.4.其他线程发送信号
1.5.清空信号集合
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
pthread_t t1,t2;
sigset_t sigs;
void handle(int s)
{
printf("信号!\n");
}
void*r1(void*d)
{
int s;
while(1)
{
printf("线程--1\n");
sigwait(&sigs,&s);
printf("接收到信号:%d!\n",s);
}
}
void*r2(void*d)
{
while(1)
{
printf("线程----2\n");
sleep(2);
pthread_kill(t1,SIGUSR1);
}
}
main()
{
sigemptyset(&sigs);
//sigaddset(&sigs,SIGUSR1);
sigfillset(&sigs);
//signal(SIGUSR1,handle);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
}
案例: sigwait实际处理了信号 如果进程没有处理信号,目标线程也没有sigwait ,则进程会接收信号进行默认处理
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sched.h>
sigset_t sigs;
pthread_t todd,teven;
void* runodd(void *d)
{
int i=0;
int s;
for(i=0;;i+=2)
{
printf("%d\n",i);
sigwait(&sigs,&s);
}
}
void* runeven(void *d)
{
int i=0;
int s;
for(i=1;;i+=2)
{
printf("%d\n",i);
sleep(1);
pthread_kill(todd,34);
}
}
main()
{
sigemptyset(&sigs);
sigaddset(&sigs,34);
pthread_create(&todd,0,runodd,0);
pthread_create(&teven,0,runeven,0);
pthread_join(todd,(void**)0);
pthread_join(teven,(void**)0);
}
2.条件量 信号量类似 2.1.定义条件量 2.2.初始化条件量 2.3.等待条件量 2.4.其他线程修改条件量 2.5.释放条件量案例: 创建两个线程. 一个线程等待信号 一个线程每隔1秒发送信号 1.使用pause+pthread_kill
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_t t1,t2;
void handle(int s) //什么也不干。但是可以防止t1没有sigwait()处理信号,程序异常退出。
{
}
void *r1(void* d)
{
while(1)
{
pause(); //pause受信号影响,不起作用了。所以程序一直循环打印。
printf("活动!\n");
}
}
void *r2(void* d)
{
while(1)
{
sleep(1);
pthread_kill(t1,34);
}
}
main()
{
signal(34,handle);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
}
上面这种方式看起来不太好,有一个怪怪的函数,什么也没干。下面用sigwait来处理:
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_t t1,t2;
sigset_t sigs;
void *r1(void* d)
{
int s;
while(1)
{
sigwait(&sigs,&s);
printf("活动!\n");
}
}
void *r2(void* d)
{
while(1)
{
sleep(1); //sigwait必须先于pthread_kill执行,
//才能正确接收到信号,不然也会异常退出。
//所以睡眠一会儿,保证每次kill之前都已经wait了。
pthread_kill(t1,34);
}
}
main()
{
sigemptyset(&sigs);
sigaddset(&sigs,34);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
}
上面这种方法的缺陷如sleep()后面的注释。看下面条件量的处理:条件量则没有上面的那个问题,不用等wait先执行。
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_t t1,t2;
pthread_cond_t cond;//1.
pthread_mutex_t m;
void *r1(void* d)
{
int s;
while(1)
{
pthread_cond_wait(&cond,&m); //在非互斥的线程里面,m参数形同虚设。
//如果在互斥的线程里,m可以解锁,让另一个线程执行,
//并发出信号让wait接收,防止死锁。
printf("活动!\n");
}
}
void *r2(void* d)
{
while(1)
{
pthread_cond_signal(&cond); //条件量不累计,发多个也相当于一个的效果。
pthread_cond_signal(&cond);
pthread_cond_signal(&cond);
sleep(10);
}
}
main()
{
pthread_mutex_init(&m,0);
pthread_cond_init(&cond,0);//2
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&m);
}
pthread_cond_*** 与sigwait都是进程同步控制 pthread_cond_***稳定 pthread_cond_***在环境下不会死锁.课堂练习: 使用条件量与互斥构造死锁程序.
#include <stdio.h>
#include <pthread.h>
pthread_t t1,t2;
pthread_mutex_t m1,m2;
pthread_cond_t c;
void* r1(void*d)
{
while(1)
{
pthread_mutex_lock(&m1);
printf("我是等待!\n");
pthread_cond_wait(&c,&m1); //如果这里是m2,则不能解r2的锁,造成死锁
pthread_mutex_unlock(&m1);
}
}
void* r2(void *d)
{
while(1)
{
pthread_mutex_lock(&m1);
printf("我是让你不等待!\n");
pthread_cond_signal(&c);
pthread_mutex_unlock(&m1);
}
}
main()
{
pthread_cond_init(&c,0);
pthread_mutex_init(&m1,0);
pthread_mutex_init(&m2,0);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,0);
pthread_join(t2,0);
pthread_mutex_destroy(&m2);
pthread_mutex_destroy(&m1);
pthread_cond_destroy(&c);
}
作业: 1.写一个程序: 两个线程写数据到文件. 数据格式:日期时间,线程ID\n 要求: 要求使用互斥, 保证数据正确. 体会使用互斥和不使用互斥的异同. 2.使用curses写一个多线程程序 开启26个线程.每个线程控制一个字母在屏幕上掉落 建议每隔字母的高度随机.
#include <pthread.h>
#include <curses.h>
#include <math.h>
struct AChar
{
int x;
int y;
int speed;
char a;
};
int stop=1;
pthread_t t[26];
pthread_t tid;
pthread_mutex_t m;
struct AChar a[26];
void *run(void *d)
{
int id;
static idx=-1;
idx++;
id=idx;
while(stop)
{
pthread_mutex_lock(&m);
//改变对象的y坐标
a[id].y+=a[id].speed;
if(a[id].y>=LINES)
{
a[id].y=rand()%(LINES/4);
}
pthread_mutex_unlock(&m);
sched_yield();
usleep(100000);
}
}
void * update(void *d)
{
int i=0;
while(stop)
{
erase();
//绘制屏幕上
for(i=0;i<26;i++)
{
mvaddch(a[i].y,a[i].x,a[i].a);
}
//刷屏
refresh();
usleep(10000);
}
}
main()
{
int i;
initscr();
curs_set(0);
noecho();
keypad(stdscr,TRUE);
for(i=0;i<26;i++)
{
a[i].x=rand()%COLS;
a[i].y=rand()%(LINES/4);
a[i].speed=1+rand()%3;
a[i].a=65+rand()%26;
}
pthread_mutex_init(&m,0);
pthread_create(&tid,0,update,0);
for(i=0;i<26;i++)
{
//随机产生字母与位置
pthread_create(&t[i],0,run,0);
}
getch();
stop=0;
for(i=0;i<26;i++)
{
//随机产生字母与位置
pthread_join(t[i],(void**)0);
}
pthread_join(tid,(void**)0);
pthread_mutex_destroy(&m);
endwin();
}
3.写一个程序:创建两个线程 一个线程负责找素数. 另外一个线程把素数保存到文件 要求: 找到以后,通知另外一个线程保存,停止招素数 线程保存好以后通知素数查找线程继续查找. 目的: 互斥与信号/条件量作用是不同.