C/C++ WebServer 3 Linux 多线程开发
线程概述
- 与进程 (process)类似。线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区城,其中包括初始化数据段、未初始化数据段,以及堆内存段。(传统意义上的 UNIX 进程只是多线程程序的一个特例,该进程只包含一个线程)
- 进程是 CPU 分配资源的最小单位
- 线程是操作系统调度执行的最小单位。
- 线程是轻量级的进程 (IWP:Light weight Process),在Iinux 环境下线程的本质仍是进程。
- 查看指定进程的 IWP 号:
ps -Lf $(pid)
- 进程间的信息难以共享。由于除去只读代码段外,父子进程并末共享内存,因此必须采用一些进程间通信方式,在进程问进行信息交换。
- 调用fork()来创建进程的代价相对较高,即便利用马时复制技术,仍热需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 foIk()调用在时间上的开销依然不菲。
- 线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。
- 创建线程比创建进程通常要快 10倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。
- 共享资源
- 进程ID,父进程ID
- 进程组ID,会话ID
- 用户ID,用户组ID
- 文件描述符表
- 信号处置
- 文件系统的相关信息umask,path
- 虚拟空间地址(除栈,.text)
- 非共享资源
- 线程ID
- 信号掩码
- 线程特有数据
- error变量
- 实时调度策略和优先级
- 栈、本地变量和调用链接信息
NPTL:Native POSIX Thread Librarygetconf GNU_LIBPTHREAD_VERSION
创建线程
1 |
|
1 | gcc pthread_create.c -o create -pthread |
终止线程
1 | // pthread_exit.c |
连接已终止的线程
1 |
|
线程的分离
1 | // pthread_detach |
线程取消
只有当子线程执行到取消点时,线程才会终止(系统调用,用户区到内核区)
1 |
|
线程属性
#include <pthread.h>
int
pthread_attr_init(pthread_attr_t *attr);
int
pthread_attr_destroy(pthread_attr_t *attr);
int
pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr,
size_t stacksize);
int
pthread_attr_getstack(const pthread_attr_t * restrict attr,
void ** restrict stackaddr, size_t * restrict stacksize);
1 | // pthread_attr.c |
线程同步
1 | // sellTickets.c |
临界区
Atomic
互斥锁
Mutual exclusion
Locked, unlocked
- 一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。一般情况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量,每一线程在访问同一资源时将采用如下协议:
- 针对共享资源锁定互斥量
- 访问共享资源
- 对互斥量解锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int tickets = 1000;
pthread_mutex_t mutex;
void *sell(void * args) {
// lock
while(tickets>0) {
pthread_mutex_lock(&mutex);
if (tickets>0) {
usleep(5000);
printf("tid: %ld, Selling ticket :%d\n", pthread_self(), tickets);
tickets--;
}else{
pthread_mutex_unlock(&mutex);
break;
}
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_mutex_init(&mutex, NULL);
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, sell, NULL);
pthread_create(&tid2, NULL, sell, NULL);
pthread_create(&tid3, NULL, sell, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_detach(tid1);
pthread_detach(tid2);
pthread_detach(tid3);
pthread_mutex_destroy(&mutex);
pthread_exit(NULL);
}
死锁
- 有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又都由不同的互斥量管理。当超过一个线程加锁同一组互斥量时,就有可能发生死锁。
- 两个或两个以上的进程在执行过程中,因年李共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
- 死锁的几种场景:
- 忘记释放锁
- 重复加锁
- 多线程多锁,抢占锁资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42// deadlock.c
int tickets = 1000;
pthread_mutex_t mutex;
void *sell(void * args) {
// lock
while(tickets>0) {
pthread_mutex_lock(&mutex);
if (tickets>0) {
usleep(5000);
printf("tid: %ld, Selling ticket :%d\n", pthread_self(), tickets);
tickets--;
}else{
pthread_mutex_unlock(&mutex);
break;
}
// pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
pthread_mutex_init(&mutex, NULL);
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, sell, NULL);
pthread_create(&tid2, NULL, sell, NULL);
pthread_create(&tid3, NULL, sell, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_detach(tid1);
pthread_detach(tid2);
pthread_detach(tid3);
pthread_mutex_destroy(&mutex);
pthread_exit(NULL);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56// deadlock1.c
// 创建2个互斥量
pthread_mutex_t mutex1, mutex2;
void * workA(void * arg) {
pthread_mutex_lock(&mutex1);
sleep(1);
pthread_mutex_lock(&mutex2);
printf("workA....\n");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
void * workB(void * arg) {
pthread_mutex_lock(&mutex2);
sleep(1);
pthread_mutex_lock(&mutex1);
printf("workB....\n");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
return NULL;
}
int main() {
// 初始化互斥量
pthread_mutex_init(&mutex1, NULL);
pthread_mutex_init(&mutex2, NULL);
// 创建2个子线程
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, workA, NULL);
pthread_create(&tid2, NULL, workB, NULL);
// 回收子线程资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
// 释放互斥量资源
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
return 0;
}
读写锁
- 在对数据的读写操作中,更多的是读操作:写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。
- 读写锁的特点:
- 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
- 如果有其它线程写数据,则其它线程都不允许读、写操作。
- 写是独占的,写的优先级高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// rwlock.c
int num=0;
pthread_rwlock_t rwlock;
void * writeNum(void* args) {
while(1) {
pthread_rwlock_wrlock(&rwlock);
num++;
printf("write, tid:%ld, num:%d\n", pthread_self(), num);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}
return NULL;
}
void * readNum(void* args) {
while(1) {
pthread_rwlock_rdlock(&rwlock);
printf("write, tid:%ld, num:%d\n", pthread_self(), num);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}
return NULL;
}
int main() {
pthread_rwlock_init(&rwlock, NULL);
pthread_t wtids[3], rtids[5];
for (int i=0; i<3; i++) {
pthread_create(&wtids[i], NULL, writeNum, NULL);
}
for (int i=0; i<5; i++) {
pthread_create(&rtids[i], NULL, readNum, NULL);
}
for (int i=0; i<3; i++) {
pthread_detach(wtids[i]);
}
for (int i=0; i<5; i++) {
pthread_detach(rtids[i]);
}
pthread_exit(NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
生产者和消费者模型
条件变量
1 | // pthread_cond.c |
信号量
1 | // semaphore.c |
- Post title:C/C++ WebServer 3 Linux 多线程开发
- Post author:Meqt
- Create time:2022-12-17 18:40:08
- Post link:https://meqtmac.github.io/2022/12/17/WebServerNote/webserver-note3/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.