多线程编程
线程是任务调度和执行的基本单位。
线程的特点:
线程切换的开销很低——实质是函数的切换
线程通信机制简答——全局变量
线程操作:
1.线程函数是由谁提供的?
非OS,而是线程库libpthread.a/.so
线程控制函数有:pthread_create、pthread_join、pthread_detach、pthread_cancel、pthread_exit等
2.线程库和函数手册的安装
sudo adt-get install glibc-doc:安装线程库
sudo apt-get install manpages-posix-dev:安装线程库的函数手册
3.线程创建
4.线程退出
5.线程等待
6.线程状态
线程同步:
1.线程vs进程
进程:进程空间天然是独立的,因此进程间资源的保护是天然的(现成的),需要重点关心的是进程间通信
线程:多线程天然的共享进程空间,因此线程数据共享是天然的(现成的),需要重点关心的是资源的保护
2.线程的资源保护机制
1)互斥锁(只能在线程里面用)
互斥锁使用的步骤:
定义一个互斥锁(变量)
初始化互斥锁:预设互斥锁的初始值
加锁解锁
进程退出时销毁互斥锁
2)线程信号量
线程信号量使用步骤:
定义信号量集合
初始化集合中的每个信号量
p、v操作
进程结束时,删除线程信号量集合
p操作:
` #include <semaphore.h>
int sem_wait(sem_t *sem);//阻塞p操作`
功能:
阻塞p操作集合中某个信号量,值-1
如果能够p操作成功最好,否则就阻塞直到p操作操作成功为止。
返回值:
成功返回0,失败返回-1,errno被设置。
参数:
p操作的某个信号量。
比如:sem_wait(&sem[0]);
sem_wait的兄弟函数
int sem_trywait(sem_t *sem):不阻塞
如果能够p操作就p操作,如果不能p操作就出错返回,不会阻塞。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
可以设置阻塞时间,如果能够p操作就p操作,不能就阻塞,如果在设置的时间内好没有
p操作成功就是出错返回,不再阻塞。
v操作
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:
对某个信号量进行v操作,v操作不存在阻塞问题。
v操作成功后,信号量的值会+1
返回值:成功返回0,失败返回-1,errno被设置。
代码演示
sem_post(&sem[0]);
3)条件变量(在互斥锁的配合下才能工作)
条件变量的作用(线程协同)
条件变量使用步骤:
· 定义一个条件变量(全局变量)由于条件变量需要互斥锁的配合,所以还需要定义一个线程互斥锁。
· 初始化条件变量
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
功能:
初始化条件变量,与互斥锁的初始化类似。
pthread_cond_t cond; //定义条件变量
pthread_cond_init(&cond, NULL); //第二个参数为NULL,表示不设置条件变量的属性。
也可以直接初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//与互斥锁的初始化的原理是一样的
返回值:成功返回0,失败返回非零错误号
参数:
- cond:条件变量
- attr:用于设置条件变量的属性,设置为NULL,表示使用默认属性
· 使用条件变量
等待条件的函数
函数原型
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
功能:
检测条件变量cond,如果cond没有被设置,表示条件还不满足,别人还没有对cond进行设置,此时
pthread_cond_wait会休眠(阻塞),直到别的线程设置cond表示条件准备好后,才会被唤醒。
返回值:成功返回0,失败返回非零错误号
参数
- cond:条件变量
- mutex:和条件变量配合使用的互斥锁
pthread_cond_wait的兄弟函数
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
多了第三个参数,用于设置阻塞时间,如果条件不满足时休眠(阻塞),但是不会一直休眠, 当时间超时后,如果cond还没有被设置,函数不再休眠。
设置条件变量的函数
函数原型
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
功能:
当线程将某个数据准备好时,就可以调用该函数去设置cond,表示条件准备好了, pthread_cond_wait检测到cond被设置后就不再休眠(被唤醒),线程继续运行,使用别的线程准备好的数据来做事。
当调用pthread_cond_wait函数等待条件满足的线程只有一个时,就是用pthread_cond_signal来唤醒,如果说有好多线程都调用pthread_cond_wait在等待时,使用int pthread_cond_broadcast(pthread_cond_t *cond);
它可以将所有调用pthread_cond_wait而休眠的线程都唤醒。
· 删除条件变量,也需要把互斥锁删除。
进程vs线程
1.多线程比多进程成本低,但性能更低
多进程是立体交通系统,虽然造价高,上坡下坡多耗电油,但是不堵车。
多线程是平面交通系统,造价低,但红绿灯太多,老堵车。
2.区别
进程是资源分配的最小单位,线程是任务调度的最小单位;
每个进程拥有独立的地址空间,多个线程共享进程地址空间:
线程之间切换比进程之间切换开销少;
线程的调度必须通过频繁加锁来保持同步,影响了线程并发性能;
进程比线程更健壮,多进程之间相互独立,进程的异常对其他进程无影响,一个线程的崩溃可能影响其他线程或者整个程序;
线程之间的通信更方便(小数据量),同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
多线程的代码结构比多进程代码结构易读;
3.如何选择?
需要频繁创建销毁的优先用线程
高性能交易服务器中间件,如TUXEDO,都是主张多进程的
需要进行大量计算的优先使用线程
强相关的处理用线程,弱相关的处理用进程
多机分布的y用进程,多核分布的用线程
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <pthread.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <signal.h>
11
12
13 #define SECON_PTH_NUMS 2 //次线程数量
14 #define PTHEXIT -1
15
16
17 /* 传递给线程的参数 */
18 typedef struct pthread_arg
19 {
20 pthread_t tid;//存放线程tid
21 int pthno;//我自己定义的编号
22 int fd;//文件描述符
23 }ptharg;
24
25
26
27 struct gloable_va
28 {
29 ptharg pth_arg[SECON_PTH_NUMS];//结构体数组,每个元素会被当做参数传递给对应的次线程
30 int pth_exit_flg[SECON_PTH_NUMS];//每个元素存放对应编号线程的退出状态
31 pthread_attr_t attr;//存放线程新属性
32 }glbva;
33
34 void print_err(char *str, int line, int err_no)
35 {
36 printf("%d, %s:%s", line, str, strerror(err_no));
37 exit(-1);
38 }
39
40 /* 线程退出处理函数 */
41 void pth_exit_deal(void *arg)
42 {
43 pthread_t tid = ((ptharg *)arg)->tid;
44
45 printf("!!! pthread %lu exit\n", tid);
46 }
47
48 void *pth_fun(void *pth_arg)
49 {
50 int fd = ((ptharg *)pth_arg)->fd;
51 int pthno = ((ptharg *)pth_arg)->pthno;
52 pthread_t tid = ((ptharg *)pth_arg)->tid;
53
54 //pthread_detach(pthread_self());//线程把自己分离出去
55
56 //注册线程退出处理函数
57 pthread_cleanup_push(pth_exit_deal, pth_arg);
58
59 printf("pthno=%d, pthread_id=%lu\n", pthno, tid);
60
61 while(1)
62 {
63 write(fd, "hello ", 6);
64 write(fd, "world\n", 6);
65 //检测退出状态
66 if(glbva.pth_exit_flg[pthno] == PTHEXIT) break;
67 }
68
69
70 pthread_cleanup_pop(!0);
71 return NULL;
72 pthread_exit((void *)10);
73 }
74
75 void signal_fun(int signo)
76 {
77 if(SIGALRM == signo)
78 {
79 int i = 0;
80 for(i=0; i<SECON_PTH_NUMS; i++)
81 {
82 //pthread_cancel(glbva.pth_arg[i].tid);//取消次线程
83 glbva.pth_exit_flg[i] = PTHEXIT;//设置为退出状态
84 }
85 }
86 else if(SIGINT == signo)
87 {
88 exit(0);
89 }
90 }
91
92 void process_exit_deal(void)
93 {
94 /* 销毁线程的属性设置 */
95 int ret = 0;
96 ret = pthread_attr_destroy(&glbva.attr);
97 if(ret != 0) print_err("pthread_attr_destroy fail", __LINE__, ret);
98
99 printf("\nprocess exit\n");
100 }
101
102 int main(void)
103 {
104 int fd = 0;
105 int i = 0;
106 int ret = 0;
107
108 //注册进程退出处理函数,exit正常终止进程时弹栈调用
109 atexit(process_exit_deal);
110
111 //打开文件,供线程操作,所有的线程(函数)可以共享打开的文件描述符
112 fd = open("./file", O_RDWR|O_CREAT|O_TRUNC, 0664);
113 if(fd == -1) print_err("open ./file fail", __LINE__, errno);
114
115 /* 初始化护attr, 设置一些基本的初始值 */
116 ret = pthread_attr_init(&glbva.attr);
117 if(ret != 0) print_err("pthread_attr_init fail", __LINE__, ret);
118
119 /* 设置分离属性 */
120 ret = pthread_attr_setdetachstate(&glbva.attr, PTHREAD_CREATE_DETACHED);
121 if(ret != 0) print_err("pthread_attr_setdetachstate fail", __LINE__, ret);
122
123 /* 通过循环创建两个次线程 */
124 for(i=0; i<SECON_PTH_NUMS; i++)
125 {
126 glbva.pth_arg[i].fd = fd;//保存文件描述符
127 glbva.pth_arg[i].pthno = i; //我自己给的线程编号
128 //创建好次线程后,讲次线程分离
129 ret = pthread_create(&glbva.pth_arg[i].tid, &glbva.attr, pth_fun, (void *)&glbva.pth_arg[i]);
130 if(ret != 0) print_err("pthread_create fail", __LINE__, ret);
131 }
132
133 printf("main tid = %lu\n", pthread_self());
134
135 signal(SIGINT, signal_fun);
136
137 /* 定时5秒,时间到后取消次线程 */
138 signal(SIGALRM, signal_fun);
139 alarm(3);
140
141 #if 0
142 void *retval = NULL;
143 for(i=0; i<SECON_PTH_NUMS; i++)
144 {
145 //阻塞等待此线程结束,回收次线程资源,并通过第二个参数接受返回值
146 pthread_join(glbva.pth_arg[i].tid, &retval);
147 printf("@@ %ld\n", (long)retval);
148 }
149 #endif
150
151
152 while(1)
153 {
154 write(fd, "hello ", 6);
155 write(fd, "world\n", 6);
156 }
157
158 return 0;
159 }