cha30.线程:线程同步

读书笔记

条件变量

一般搭配一个条件和一个互斥量使用

经典的使用方式如下

  • 对于需要在某某条件下运行的线程,先对mutex加锁,以读取condition
    • 若满足,则在预期状态下执行后续操作,结束后对mutex解锁
    • 若不满足,则pthread_cond_wait
      • 此时该函数会先解锁mutex(允许其他进程获取mutex以修改状态)
      • 陷入阻塞,直到其他线程调用pthread_cond_signalpthread_cond_broadcast唤醒
      • 唤醒后,获取mutex锁,以检查条件是否满足,满足则执行后续,不满足继续调用wait阻塞(故此处需要用while
  • 对于可以改变某某条件的线程
    • 获取mutex
    • 改变condition
    • signal/broadcast
    • unlock(unlock与上一步顺序可调换)
1
2
3
4
5
6
7
8
9
10
11
12
// one thread
pthread_mutex_lock(&mutex);
while(!condition)
pthread_cond_wait(&cond, &mutex);
/* condition matched, execute task */
pthread_mutex_unlock(&mutex);

// other thread
pthread_mutex_lock(&mutex);
condition = xxxx; // change condition
pthread_cond_signal(&cond); // or pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex); // 以上两行顺序任意,
阅读更多

cha29.线程:介绍

29.1

若一线程执行了如下代码,可能会产生什么结果?
pthread_join(pthread_self(),NULL);
在 Linux上编写一个程序,观察一下实际会发生什么情况。假设代码中有一变量 tid,其中包含了某个线程ID,在自身发起pthread_join(tid, 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
#define CHECK(x) do { \
if(!(x)) { \
fwritef(STDERR_FILENO, "error: %s\n", strerror(errno)); \
pthread_exit(NULL); \
} \
} while(0)
void *thread1_fun(void * args) {
sleep(1);
return NULL;
}

void *thread_fun(void * args) {
void *ret;
printf("thread: before,join-pthread_self()\n");
CHECK(pthread_join(pthread_self(), &ret) == 0);
printf("thread: after,join-pthread_self()\n");
return NULL;
}

int main() {
pthread_t thread;
CHECK(pthread_create(&thread, NULL, thread_fun, NULL) == 0);
CHECK(pthread_detach(thread) == 0);
printf("main: pthread_created.\n");
sleep(10);
return 0;
}

结果

pthread_join返回值非0,出现错误,errno表示SUCCESS

为啥是success呢,errno不是一个线程一份吗,还是这个函数不算系统调用??可能是他调用了waitpid,但是出错位置不在waitpid

阅读更多

cha27.进程执行

exec()

  • 带e的可以指定环境变量,否则继承
  • 带p的允许只提供文件名,允许提供不带"/"的路径,在path中寻找
    • 若无env PATH默认为.:/usr/bin/:/bin
    • 从左往右搜索,直到找到为止
  • 带l的用不定长参数(参数列表),以NULL结尾
    • execle在NULL后面接envp数组

exec执行脚本

当exec第一个参数文件以"#!"开始,则会读取该行进行解析

1
#!<interpreter-path> [arg] <script> <script-args...> 

阅读更多

cha26.监控子进程

孤儿与僵尸

  • 孤儿: 子进程结束前父进程未wait结束的进程。其父进程会变成1由init接管进行wait
  • 僵尸: 父进程未结束,子进程已经结束,且父进程未执行wait。系统保留僵尸的进程表记录,以备未来父进程需要wait获取其结束状态
    • 无法被kill,只能kill其父进程

1

编写一程序以验证当一子进程的父进程终止时,调用getppid()将返回1(进程 init的进程ID)。

无聊,不弄

2

阅读更多

cha24.进程的创建

1

执行下面代码后会产生多少新进程

1
2
3
fork();
fork();
fork();

$ 2^3 -1 = 7 $

1
2
3
fork(); // A,产生B, A+B
fork(); // A产生C,B产生D, A+B+C+D
fork(); // ABCD产生EFGH,A+B+C+D+E+F+G+H

2

阅读更多

cha22.信号:高级特性

读书笔记

核心转储文件

  • 特定信号会引发进程创建核心转储文件(工作目录)并终止。

  • 核心转储文件(core)是内存映像的一个文件,利用调试器可以查看到退出时的代码、数据状态。

  • P371展示了不会产生核心转储文件的情况,大致为

    • 没有写权限
    • 存在硬链接大于1的同名文件
    • 目录不存在
    • ulimit等限制为0
    • 二进制程序没有读权限
    • 工作目录的挂载方式为只读
    • set-user/group-ID程序运行者为非文件属主(组)
  • /proc/sys/kenel/core_pattern中存储了核心转储文件的命名格式化字符串

信号处理、传递特殊情况

  • SIGKILL和SIGSTOP的默认行为无法改变,无法阻塞。总是可以使用该信号处理失控进程。

    • 前面读书不认真
    • 信号阻塞即对该信号的传递延后,直到该信号从掩码中移除。
    • 除非是实时信号,否则不对阻塞信号排队,恢复信号后只传递该信号一次
  • SIGCONT恢复停止的进程

    • SIGCONT总会恢复运行,不论该信号是否被阻塞或忽略
    • 在停止的进程恢复之前,若有其他进程传递其他信号,则该信号并未被真实传递。(除了sigkill)
    • 收到SIGCONT时,处于等待状态的停止信号将会被丢弃。反过来,收到停止信号后,等待状态的SIGCONT也会被丢弃
  • 若由终端产生的信号(SIGHUP SIGINT SIGQUIT SIGTTIN SIGTTOU SIGTSTP)被忽略,则不应该改变其信号处置(处理函数)

    • 这个很难懂,后面34章会讲

sigkill的力所不能及

阅读更多

LeetCode-26

[Medium] 1110. 删点成林

分析

  1. 使用什么样的数据结构
    1. 直接用数组
    2. 用孩子兄弟表示法
  2. 使用什么样的遍历方法?

代码

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
class Solution {
public:
vector<TreeNode*> forest;
vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
if(root){
if(del(root, to_delete)) {
push_forest(root);
} else {
forest.push_back(root);
}
}
return forest;
}
bool del(TreeNode* root, vector<int>& to_delete) {
if(root->left && del(root->left, to_delete)) {
push_forest(root->left);
root->left = nullptr;
}
if(root->right && del(root->right, to_delete)) {
push_forest(root->right);
root->right = nullptr;
}
for(int d : to_delete) {
if(d == root->val) {
return true;
}
}
return false;
}
void push_forest(TreeNode *root) {
if(root->left) {
forest.push_back(root->left);
}
if(root->right) {
forest.push_back(root->right);
}
}
};

结果

阅读更多

cha21.信号:信号处理器函数

21.1

实现abort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void __abort(void) {
fflush(NULL);
// 随便输出点什么吧
void *buff = malloc(BUFSIZ);
int cd = open("coredump", O_RDWR | O_CREAT, 0644);
int mem = open("/proc/self/stack", O_RDONLY);
size_t readsize;
while((readsize = read(mem, buff, BUFSIZ)) > 0) {
write(cd, buff, readsize);
}
close(cd);
close(mem);
// 后面这三行+fflush就够了吧
printf("raise SIGABRT\n");
raise(SIGABRT);
printf("signal SIG_DFL\n");
signal(SIGABRT, SIG_DFL);
printf("raise SIGABRT\n");
raise(SIGABRT);
printf("__abort return\n");
}

读后感

可重入问题

这一章首先讲了信号处理器函数的可重入问题。这是由于执行信号处理器函数时,有可能再次触发信号,调用该函数。

阅读更多

LeetCode-25

[hard] 1377. T 秒后青蛙的位置

题目分析

题目强调为一颗无向树,每次访问未访问过的节点。也就是说,每秒若有子节点,则跳到子节点,否则呆在原地不动。

也就是根据题目构造一棵根节点为1的树,并按照层次遍历该树即可。但是题目输入的边并不一定以1为根节点。

代码

阅读更多

cha20.信号:基本概念

20.2

展示SIG_IGN一定不会收到信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// Created by root on 5/23/23.
//

#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
signal(SIGINT, SIG_IGN);
printf("set SIGINT(%s) as SIG_IGN. always ignore ctrl-c\n", strsignal(SIGINT));
for(int i = 16; i >= 0; i--) {
sleep(1);
printf("sleep %ds, try press ctrl-c\n", i);
}
signal(SIGINT, SIG_DFL);
printf("set SIGINT(%s) as SIG_DFL. always take default action for ctrl-c\n", strsignal(SIGINT));
for(;;) {
usleep(500000);
printf("try press ctrl-c\n");
}
return 0;
}

20.3

展示sigaction时,sa_nodefersa_resethand的作用

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
//
// Created by root on 5/23/23.
//

#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void sigint_handler(int sig) {
if(sig != SIGINT) {
return;
}
printf("喵!\n");
sleep(1);
// sa_nodefer处理过程中,不阻塞sigint
// 此时连续按ctrl-c,可以喵很多次
printf("汪!\n");
}


int main() {
sigaction(SIGINT, &(struct sigaction){
.sa_handler = sigint_handler,
.sa_flags = SA_NODEFER,
}, NULL);
printf("set SIGINT(%s) as SA_NODEFER\n", strsignal(SIGINT));
for(int i = 16; i >= 0; i--) {
sleep(1);
printf("sleep %ds, try press ctrl-c\n", i);
}
sigaction(SIGINT, &(struct sigaction){
.sa_handler = sigint_handler,
.sa_flags = SA_RESETHAND,
}, NULL);
printf("set SIGINT(%s) as SA_RESETHAND\n", strsignal(SIGINT));
for(;;) {
usleep(500000);
printf("try press ctrl-c\n");
}
return 0;
}
阅读更多