linux之线程
Posted 小瑞的学习笔记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux之线程相关的知识,希望对你有一定的参考价值。
文章目录
线程
1. 为什么使用线程
- 使用fork创建进程以执行新的任务,该方式的代价很高(会复制父进程的资源)。
- 多个进程间不会直接共享内存
- 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,进程要想执行任务,必须得有线程,进程至少要有一条线程,程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程
2. 什么是线程
类比:
-
创建一个进程,类似于“克隆”一个家庭。该“家庭”与原来的家庭完全相同但是新“家庭”和原来的家庭完全独立。
-
进程包含一个或多个线程。 类似与一个家庭,包含一个或多个家庭成员。家庭内的各成员同时做各自的事情(父亲工作、母亲持家、小孩上学)而对于家庭外部的人来说,这个家庭同时在做多件事情。
-
家庭内的每个成员,就是一个线程。各个家庭成员有自己的个人资源(线程有自己的局部变量)但是所有家庭成员都能共享这个家庭的资源:房子、汽车、家庭的公共资金。(同一个进程内的各个线程,能够共享整个进程的全局变量,除了线程的局部变量外,其他资源都共享)
3. 线程的优点、缺点
优点: 创建线程比创建进程,开销要小。
缺点: 1)多线程编程,需特别小心,很容易发生错误。
2)多线程调试很困难。
3)把一个任务划分为两部分, 用两个线程在单处理器上运行时,不一定更快。
除非能确定这两个部分能同时执行、且运行在多处理器上。
4. 线程的应用场合
1. 当一个应用程序,需要同时处理输入、计算、输出时,
可开3个线程,分别处理输入、计算、输出。
让用户感觉不到等待。
2. 高并发编程。
5. 线程的使用API
1)线程的创建
原型:int pthread_create (pthread_t *thread,pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
参数:thread, 指向新线程的标识符。是一个传出参数
attr, 用来设置新线程的属性。
一般取默认属性,即该参数取NULL
start_routine, 该线程的处理函数
该函数的返回类型和参数类型都是void*
arg, 线程处理函数start_routine的参数
功能:创建一个新线程,
同时指定该线程的属性、执行函数、执行函数的参数
通过参数1返回该线程的标识符。
返回值:成功,返回0
失败,返回错误代码
注意:大部分pthread_开头的函数成功时返回0,失败时返回错误码(而不是-1)
注意:使用fork创建进程后,进程马上就启动,但是是和父进程同时执行fork后的代码。
使用pthread_create创建线程后,新线程马上就启动,即执行对应的线程处理函数。
2)线程的终止
原型:void pthread_exit (void *retval)
功能:在线程函数内部调用该函数。
终止该线程,并通过参数retval返回一个指针。
该指针不能指向该线程的局部变量。
3)等待指定线程结束
pthread_join
功能:类似与进程中的waitpid
等待指定的线程结束,并使参数指向该线程函数的返回值(用pthread_exit返回的值)
原型:int pthread_join (pthread_t th,
void ** thread_return);
参数:th, 指定等待的线程
thread_return, 指向该线程函数的返回值
线程函数的返回值类型为void*,故该参数的类型为void**
4)使用线程程序的编译
- (1) 编译时,定义宏_REENTRANT
即: gcc -D_REENTRANT (#define REENTRANT)- 功能:告诉编译器,编译时需要可重入功能。即使得,在编译时,编译部分函数的可重入版本。
注:在单线程程序中,整个程序都是顺序执行的,一个函数在同一时刻只能被一个函数调用,但在多线程中,由于并发性,一个函数可能同时被多个函数调用,此时这个函数就成了临界资源,很容易造成调用函数处理结果的相互影响,如果一个函数在多线程并发的环境中每次被调用产生的结果是不确定的,我们就说这个函数是"不可重入的"/"线程不安全"的。
- (2) 编译时,指定线程库
- 即: gcc -lpthread
- 总结:一般使用如下形式即可
gcc myThreadCode.c -o myThreadCode -D_REENTRANT -lpthread
实例 main1.c
这里只是对线程创建,销毁的一个基本运用
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int my_global;
void* my_thread_handle(void *arg)
int val;
val = *((int*)arg);
printf("new thread begin, arg=%d\\n", val);
my_global += val;
sleep(3);
pthread_exit(&my_global);// 结束线程
// 不再执行
printf("new thread end\\n");
int main(void)
pthread_t mythread;// 传出参数
int arg;
int ret;
void *thread_return;// 线程返回值
arg = 100;
my_global = 1000;
printf("my_global=%d\\n", my_global);
printf("ready create thread...\\n");
// 创建线程
ret = pthread_create(&mythread, 0, my_thread_handle, &arg);
if (ret != 0)
printf("create thread failed!\\n");
exit(1);
printf("wait thread finished...\\n");
// 等待 线程结束 并且获取返回值
ret = pthread_join(mythread, &thread_return);
if (ret != 0)
printf("pthread_join failed!\\n");
exit(1);
printf("wait thread end, return value is %d\\n", *((int*)thread_return));
printf("my_global=%d\\n", my_global);
printf("create thread finished!\\n");
6. 线程同步
- 线程同步其实实现的是线程排队。
- 防止线程同步访问共享资源造成冲突。
- 多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。
1. 问题
- 同一个进程内的各个线程,共享该进程内的全局变量
- 如果多个线程同时对某个全局变量进行访问时,有可能达不到预期效果。
2. 信号量和互斥量的选择。
- 互斥量:为协调共同对一个共享资源的单独访问而设计的;因为进入内核模式,所以性能比临界区差;跨进程,可用于防止程序重复打开运行。
- 信号量:为控制一个具有有限数量用户资源而设计,互斥锁可以理解为1个用户资源的信号量。
- 使用时,选择更符合语义的手段:
- 如果要求最多只允许一个线程进入临界区,则使用互斥量
- 如果要求多个线程之间的执行顺序满足某个约束,则使用信号量
- 使用时,选择更符合语义的手段:
条件变量:条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
信号量
1)什么是信号量
-
此时所指的“信号量”是指用于同一个进程内多个线程之间的信号量。即POSIX信号量,而不是System V信号量(用于进程之间的同步)
-
用于线程的信号量的原理,与用于进程之间的信号量的原理相同。都有P操作、V操作。
-
信号量的表示:sem_t 类型
2) 信号量的初始化
原型:int sem_init (sem_t *sem,int pshared, unsigned int value); 功能:对信号量进行初始化 参数:sem, 指向被初始化的信号量 pshared, 0:表示该信号量是该进程内使用的“局部信号量”, 不再被其它进程共享。 非0:该信号量可被其他进程共享,Linux不支持这种信号量 value, 信号量的初值。>= 0 返回值:成功,返回0 失败, 返回错误码
3) 信号量的P操作
原型:int sem_wait (sem_t *sem); 返回值:成功,返回0 失败, 返回错误码
4) 信号量的V操作
原型:int sem_post (sem_t *sem); 返回值:成功,返回0 失败, 返回错误码
5) 信号量的删除
原型:int sem_destroy (sem_t *sem); 返回值:成功,返回0 失败, 返回错误码
6) 实例
主线程循环输入字符串,把字符串存放到一个全局缓存中。新线程从全局缓存中读取字符串,统计该字符串的长度。直到用户输入end
main.c
预期结果:主线程每接收终端输入的一个字符串,子线程就打印字符串和输出字符串长度
- 信号量被初始化为0,主线程接受一个字符串的时候,执行V操作(信号量+1),此时信号量大于1,子线程执行P操作(-1),然后对字符串做出相应的操作
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define BUFF_SIZE 80
// 全局变量可以让多个线程访问
char buff[BUFF_SIZE];
sem_t sem;
static void* str_thread_handle(void *arg)
while(1)
//P(sem) -1
if (sem_wait(&sem) != 0)
printf("sem_wait failed!\\n");
exit(1);
printf("string is: %slen=%u\\n", buff, (unsigned int)strlen(buff));
if (strncmp(buff, "end", 3) == 0)
break;
int main(void)
int ret;
pthread_t str_thread;
void *thread_return;
// 参数:被初始化信号量 0 给信号量的赋值
ret = sem_init(&sem, 0, 0);
if (ret != 0)
printf("sem_init failed!\\n");
exit(1);
// 创建线程
ret = pthread_create(&str_thread, 0, str_thread_handle, 0);
if (ret != 0)
printf("pthread_create failed!\\n");
exit(1);
while (1)
// 从终端获取一行输入
fgets(buff, sizeof(buff), stdin);
//V(sem) +1
// 0->1 那么线程就可以执行P操作 1->0
if (sem_post(&sem) != 0)
printf("sem_post failed!\\n");
exit(1);
if (strncmp(buff, "end", 3) == 0)
break;
ret = pthread_join(str_thread, &thread_return);
if (ret != 0)
printf("pthread_join failed!\\n");
exit(1);
ret = sem_destroy(&sem);
if (ret != 0)
printf("sem_destroy failed!\\n");
exit(1);
return 0;
练习
创建2个线程(共有主线程、线程1、线程2共3个线程)
主线程阻塞式等待用户输入字符串
主线程每接收到一个字符串之后, 线程1就马上对该字符串进行处理。
线程1的处理逻辑为:统计该字符串的个数,并记录当时的时间。
线程1把该字符串处理完后,线程2马上就把处理结果写入文件result.txt
直到用户输入exit.
multi_pthread.c
互斥量
1)什么是互斥量
效果上等同于初值为1的信号量
互斥量的使用:类型为 pthread_mutex_t
2)互斥量的初始化
原型:int pthread_mutex_init(pthread_mutex_t *mutex,
pthread_mutexattr_t *attr);
参数:mutex, 指向被初始化的互斥量
attr, 指向互斥量的属性
一般取默认属性(当一个线程已获取互斥量后,该线程再次获取该信号量,将导致死锁!)
3) 互斥量的获取
原型:int pthread_mutex_lock (pthread_mutex_t *mutex);
4)互斥量的释放
原型:int pthread_mutex_unlock (pthread_mutex_t *mutex);
5)互斥量的删除
int pthread_mutex_destroy (pthread_mutex_t *mutex);
- 实例
main3.c
预期实现效果:主线程和子线程依次把全局变量 -1 ,依次往终端输出结果
- 当没有用互斥量的时候,同时运行主线程和子线程,对全局变量进行 - 1
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#define BUFF_SIZE 80
// 全局
int global_value = 1000;
pthread_mutex_t lock;
static void* str_thread_handle(void *arg)
int i = 0;
for (i=0; i<10; i++)
//pthread_mutex_lock(&lock);
if (global_value > 0)
// work
sleep(1);
printf("soled ticket(%d) to ChildStation(%d)\\n",
global_value, i+1);
global_value--;
//pthread_mutex_unlock(&lock);
sleep(1);
int main(void)
int ret;
pthread_t str_thread;
void *thread_return;
int i;
ret = pthread_mutex_init(&lock, 0);
if (ret != 0)
printf("pthread_mutex_init failed!\\n");
exit(1);
ret = pthread_create(&str_thread, 0, str_thread_handle, 0);
if (ret != 0)
printf("pthread_create failed!\\n");
exit(1);
for (i=0; i<10; i++)
//pthread_mutex_lock(&lock);
if (global_value > 0)
// work
sleep(1);
printf("soled ticket(%d) to MainStation(%d)\\n",
global_value, i+1);
global_value--;
//pthread_mutex_unlock(&lock);
sleep(1);
ret = pthread_join(str_thread, &thread_return);
if (ret != 0)
printf("pthread_join failed!\\n");
exit(1);
ret = pthread_mutex_destroy(&lock);
if (ret != 0)
printf("pthread_mutex_destroy failed!\\n");
exit(1);
return 0;
- 主线程 -10 , 子线程 -10 最后应该是 981 所以不符合预期
- 原因 主线程和子线程同时对全局变量进行了 -1
使用信号量后 - 上了两把锁
- 给主线程和子线程对全局变量进行操作的部分分别上锁
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#define BUFF_SIZE 80
// 全局
int global_value = 1000;
pthread_mutex_t lock;
static void* str_thread_handle(void *arg)
int i = 0;
for (i=0; i<10; i++)
/*****上锁*****/
pthread_mutex_lock(&lock);
if (global_value > 0)
// work
sleep(1);
printf("soled ticket(%d) to ChildStation(%d)\\n",
global_value, i+1);
global_value--;
/*****开锁*****/
pthread_mutex_unlock(&lock);
sleep(1);
int main(void)
int ret;
pthread_t str_thread;
void *thread_return;
int i;
ret = pthread_mutex_init(&lock, 0);
if (ret != 0)
printf("pthread_mutex_init failed!\\n");
exit(1);
ret = pthread_create(&str_thread, 0, str_thread_handle, 0);
if (ret != 0)
printf("pthread_create failed!\\n");
exit(1);
for (i=0; i<10; i++)
/*****上锁*****/
pthread_mutex_lock(&lock);
if (global_value > 0)
// work
sleep(1);
printf("soled ticket(%d) to MainStation(%d)\\n",
global_value, i+1);
global_value--;
/*****开锁*****/
pthread_mutex_unlock(&lock);
sleep(1);
ret = pthread_join(str_thread, &thread_return);
if (ret != 0)
printf("pthread_join failed!\\n");
exit(1);
ret = pthread_mutex_destroy(&lock);
if (ret != 0)
printf("pthread_mutex_destroy failed!\\n");
exit(1);
return 0;
- 结果分析:相比于上次,每一次输出全局变量就 -1 最后结果为 981
条件变量
1.什么是线程条件变量
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
2. 条件变量初始化
原型:int pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);
参数:cond: 条件变量指针
attr:条件变量高级属性
3. 唤醒一个等待线程
原型: int pthread_cond_signal (pthread_cond_t *cond);
参数:cond:条件变量指针
4.唤醒所有等待该条件变量的线程
原型: int pthread_cond_broadcast (pthread_cond_t *cond);
参数:cond, 条件变量指针
5.等待条件变量/超时被唤醒
原型: int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime);
参数:cond, 条件变量指针
pthread_mutex_t *mutex 互斥量
const struct timespec *abstime 等待被唤醒的绝对超时时间
6.等待条件变量被唤醒(一般使用这个)
原型: int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);
参数:cond, 条件变量指针
pthread_mutex_t *mutex 互斥量
常见错误码: [EINVAL] cond或mutex无效,
[EINVAL] 同时等待不同的互斥量
[EINVAL] 主调线程没有占有互斥量
7. 释放/销毁条件变量
pthread_cond_destroy 待销毁的条件变量
原型: int pthread_cond_destroy (pthread_cond_t *cond);
参数:cond, 条件变量指针
main.c
效果:触发信号,子线程向终端打印数据
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 注意这里一定要定义为全局变量
pthread_mutex_t mutex;
pthread_cond_t cond;
void *thread1(void *arg)
while (1)
printf("thread1 is running\\n");
// 加锁
pthread_mutex_lock(&mutex);
printf("thread1 lock..\\n");
// 解锁-阻塞等待信号-信号来了-加锁-执行任务
pthread_cond_wait(&cond, &mutex);
// 执行任务
printf("thread1 applied the condition\\n");
// 解锁
printf("thread1 unlock..\\n");
pthread_mutex_unlock(&mutex);
sleep(4);
void *四十Linux 线程——线程同步之条件变量之线程状态转换
42.1 线程状态转换
42.1.1 状态转换图
42.1.2 一个线程计算,多个线程获取的案例
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4 #include <unistd.h>
5
6 /** 两个线程定义的共享资源 */
7 typedef struct {
8 int res;
9 int counter; ///< 用于统计获取结果线程的数量
10 pthread_cond_t cond; ///< 条件变量
11 pthread_mutex_t mutex; ///< 互斥锁
12 }Result;
13
14
15 /** 计算并将结果放置在 Result 中的线程运行函数 */
16 void *set_fn(void *arg)
17 {
18 Result *r = (Result *)arg;
19 int i = 0;
20 int sum = 0;
21
22 for(; i <= 100; i++){
23 sum += i;
24 }
25
26 /** 将结果放置到 Result 中 */
27 r->res = sum;
28
29 pthread_mutex_lock(&r->mutex);
30 /** 判断获取结果的线程是否达到指定的数量 */
31 while(r->counter < 2){
32 pthread_mutex_unlock(&r->mutex);
33 usleep(100);
34 pthread_mutex_lock(&r->mutex);
35 }
36 pthread_mutex_unlock(&r->mutex);
37
38 /** 通知唤醒等待的那个获取结果的线程 */
39 pthread_cond_broadcast(&r->cond);
40
41 return (void *)0;
42 }
43
44 /** 获得结果的线程运行函数 */
45 void *get_fn(void *arg)
46 {
47 Result *r = (Result *)arg;
48
49 /** 对两个线程共享的判断条件进行保护(加锁) */
50 /** 两个线程对判断条件的操作是互斥的 */
51 pthread_mutex_lock(&r->mutex);
52 /** 有一个线程准备好了,则计数器 +1 */
53 r->counter++;
54
55 /** 获取结果的线程等待 */
56 pthread_cond_wait(&r->cond, &r->mutex);
57
58 /** 被唤醒后 */
59 pthread_mutex_unlock(&r->mutex);
60
61 /** 去获取计算结果 */
62 int res = r->res;
63 printf("0x%lx get sum is %d\\n", pthread_self(), res);
64
65 return (void *)0;
66 }
67
68 int main(void)
69 {
70 int err;
71 pthread_t cal, get1, get2;
72
73 Result r;
74 r.counter = 0;
75 pthread_cond_init(&r.cond, NULL);
76 pthread_mutex_init(&r.mutex, NULL);
77
78 /** 启动获取结果的线程 */
79 if((err = pthread_create(&get1, NULL, get_fn, (void *)&r)) != 0){
80 perror("pthread create error");
81 }
82
83 if((err = pthread_create(&get2, NULL, get_fn, (void *)&r)) != 0){
84 perror("pthread create error");
85 }
86
87 /** 启动计算结果的线程 */
88 if((err = pthread_create(&cal, NULL, set_fn, (void *)&r)) != 0){
89 perror("pthread create error");
90 }
91
92 pthread_join(cal, NULL);
93 pthread_join(get1, NULL);
94 pthread_join(get2, NULL);
95
96 pthread_cond_destroy(&r.cond);
97 pthread_mutex_destroy(&r.mutex);
98
99 pthread_cond_destroy(&r.cond);
100 pthread_mutex_destroy(&r.mutex);
101 return 0;
102 }
编译运行结果如下:
42.2 读者-写者案例
- 几种情况:
- 1 个写者,1 个读者
- 1 个写者,多个读者
- 多个写者,多个读者
完成第一种情况:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4 #include <string.h>
5 #include <unistd.h>
6
7 typedef struct {
8 int value;
9
10 /** 读者 */
11 pthread_cond_t rc;
12 pthread_mutex_t rm;
13 int r_wait;
14
15 /** 写者 */
16 pthread_cond_t wc;
17 pthread_mutex_t wm;
18 int w_wait;
19 }Storage;
20
21 /** 写入数据的函数 */
22 void set_data(Storage *s, int value)
23 {
24 s->value = value;
25 }
26
27 /** 获取数据的函数 */
28 int get_data(Storage *s)
29 {
30 return s->value;
31 }
32
33 /** 写者线程运行函数定义 */
34 void *set_th(void *arg)
35 {
36 Storage *s = (Storage *)arg;
37 int i = 1;
38 for(; i <= 100; i++){
39 /** 写入数据 */
40 set_data(s, i +100);
41 printf("0x%lx(%-5d) write data : %d\\n", pthread_self(), i, i + 100);
42
43 pthread_mutex_lock(&s->rm);
44 /** 判断读者线程是否准备好 */
45 while(!s->r_wait){
46 pthread_mutex_unlock(&s->rm);
47 sleep(1);
48 pthread_mutex_lock(&s->rm);
49 }
50 s->r_wait = 0;
51 pthread_mutex_unlock(&s->rm);
52
53 /** 通知读者线程读取数据 */
54 pthread_cond_broadcast(&s->rc);
55
56 /** 写者线程自阻塞等待读者线程通知已经读取完毕,
57 * 然后唤醒写者线程继续写入数据 */
58 pthread_mutex_lock(&s->wm);
59 s->w_wait = 1;
60 pthread_cond_wait(&s->wc, &s->wm);
61 pthread_mutex_unlock(&s->wm);
62
63 }
64 return (void *)0;
65 }
66
67 /** 读者线程运行函数定义 */
68 void *get_th(void *arg)
69 {
70 Storage *s = (Storage *)arg;
71 int i = 1;
72 for(; i <= 100; i++){
73 pthread_mutex_lock(&s->rm);
74 s->r_wait = 1;
75 pthread_cond_wait(&s->rc, &s->rm);
76 pthread_mutex_unlock(&s->rm);
77
78 /** 读者线程被唤醒后读取数据 */
79 int value = get_data(s);
80 printf("0x%lx(%-5d) read data: %d\\n", pthread_self(), i, value);
81
82 pthread_mutex_lock(&s->wm);
83 /** 判断写者线程是否准备好 */
84 while(!s->w_wait){
85 pthread_mutex_unlock(&s->wm);
86 sleep(1);
87 pthread_mutex_lock(&s->wm);
88 }
89 /** 唤醒写者线程 */
90 s->w_wait = 0;
91 pthread_mutex_unlock(&s->wm);
92 pthread_cond_broadcast(&s->wc);
93
94 }
95 return (void *)0;
96 }
97
98 int main(void)
99 {
100 int err;
101 pthread_t rth, wth;
102
103 Storage s;
104 s.r_wait = 0;
105 s.w_wait = 0;
106 pthread_mutex_init(&s.rm, NULL);
107 pthread_mutex_init(&s.wm, NULL);
108 pthread_cond_init(&s.rc, NULL);
109 pthread_cond_init(&s.wc, NULL);
110
111 /** 创建一个读者线程和写者线程 */
112 if((err = pthread_create(&rth, NULL, get_th, (void *)&s)) != 0){
113 perror("pthread create error");
114 }
115
116 if((err = pthread_create(&wth, NULL, set_th, (void *)&s)) != 0){
117 perror("pthread create error");
118 }
119
120 pthread_join(rth, NULL);
121 pthread_join(wth, NULL);
122
123 pthread_mutex_destroy(&s.rm);
124 pthread_mutex_destroy(&s.wm);
125 pthread_cond_destroy(&s.rc);
126 pthread_cond_destroy(&s.wc);
127
128 return 0;
129 }
以上是关于linux之线程的主要内容,如果未能解决你的问题,请参考以下文章