进程相关的题目:
进程在内核中的数据结构是什么?
锁相关的题目
进程占有的资源:地址空间,全局变量,打开的文件,子进程,信号量,账户信息
线程占有的资源:栈,寄存器,状态,程序计数器
最后关键:线程占有的资源不会共享,所以堆是共享的
https://www.cnblogs.com/wuchanming/p/4378189.html
这里注意下错误返回码,以及程的信号屏蔽码在不同线程间是不一样的。
生产者消费者模型最简单的模型为单个消费者,单个生产者,以及无限大的缓冲区。以下是生产者,消费者在linux C下面的实现
1.信号量的实现方式
先贴下信号量使用的函数
sem_t xxx;
sem_wait();
sem_post();
sem_init();
#include
#include
#include
#define Max 5
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
sem_t full ;
sem_t empty;
int end = 0 ;
int front = 0;
void* produce(void* arg){
while (1)
{
/* code */
sem_wait(&empty);
pthread_mutex_lock(&mutex);
end = (end + 1)%Max;
printf("now end is %d\n",end);
pthread_mutex_unlock(&mutex);
sem_post(&full);
}
}
void* consume(void* arg){
while (1)
{
sem_wait(&full);
pthread_mutex_lock(&mutex);
front = (front + 1)%Max;
printf("now front is %d\n",front);
pthread_mutex_unlock(&mutex);
sem_post(&empty);
}
}
int main(int argc, char *argv[])
{
pthread_t thid1;
pthread_t thid2;
sem_init(&full, 0, 0);
sem_init(&empty, 0, 5);
pthread_create(&thid1, NULL, produce, NULL);
pthread_create(&thid2, NULL, consume, NULL);
pthread_join(thid1, NULL);
pthread_join(thid2, NULL);
return 0;
}
gcc -g test.cpp -o main -lpthread
2.使用条件变量和互斥锁 (知乎也有https://www.zhihu.com/question/36902051)
条件变量的常见用法是在不满足某些条件时,阻塞自己,直到有线程通知自己醒来。
而互斥量在这里的作用依然还是防止多线程对共享资源同时操作,造成未知结果。
生产者消费者的行为与之前相同,只不过原来只调用sem_wait()可以完成两步,1是检查条件,2是阻塞,现在条件变量需要我们自己来设定条件(所以说条件变量配合互斥锁比信号量的功能更强大,因为它可以自定义休眠条件,但是这对使用者的要求也提高了,必须理清逻辑关系避免死锁)
这里需要关注下条件变量的底层原理
pthread_wait是先将互斥锁解开,并陷入阻塞,直到pthread_signal发出信号后pthread_cond_wait才再加上锁,然后退出。
所以条件变量是配合锁一起使用的,一般的格式是
pthread_mutex_lock(&lock);
while(xxx条件){
pthread_cond_wait(¬empty,&mutex);
}
.....
pthread_mutex_unlock(&lock);
#include
#include
#include
#define Max 5
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t notfull = PTHREAD_COND_INITIALIZER;
pthread_cond_t notempty = PTHREAD_COND_INITIALIZER;
int end = 0 ;
int front = 0;
void* produce(void* arg){
while (1)
{
/* code */
pthread_mutex_lock(&mutex);
while ((end+1)%Max == front)
{
/* code */
printf("full !!!! Productor is waiting\n");
pthread_cond_wait(¬full,&mutex);
}
end = (end + 1)%Max;
printf("now end is %d\n",end);
pthread_cond_signal(¬empty);
pthread_mutex_unlock(&mutex);
}
}
void* consume(void* arg){
while (1)
{
pthread_mutex_lock(&mutex);
while (end%Max == front)
{
printf("Empty!!! Comsumer is waiting\n");
pthread_cond_wait(¬empty,&mutex);
}
front = (front + 1)%Max;
printf("now front is %d\n",front);
pthread_cond_signal(¬full);
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, char *argv[])
{
pthread_t thid1;
pthread_t thid2;
pthread_create(&thid1, NULL, produce, NULL);
pthread_create(&thid2, NULL, consume, NULL);
pthread_join(thid1, NULL);
pthread_join(thid2, NULL);
return 0;
}
锁相关的题目(这部分内容可以联合数据库中锁来讲)
进程调度
这里面需要注意的知识点是:进程调度会把“缺乏资源”的进程置于休眠队列、又要把休眠队列中资源要求可以满足的进程置于等待队列——然后时间片一到就会调度运行。
(那么,我们可以用一些分析工具看出,多少进程阻塞在磁盘访问上了,这个是从何而来的呢?答案是,操作系统会维护一个“锁”的列表;找到这个锁的对应项,读它的相关信息,再找到申请它的进程队列,自然就知道哪些进程试图访问磁盘、但暂时得不到满足了——注意这类锁并不需要进程显式申请,相关逻辑已经包含在对应的系统调用里了)
使用方法:https://www.zhihu.com/question/66733477 用户 果冻虾仁
知乎invalid s的回答 https://www.zhihu.com/question/66733477
自旋锁:如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直申请CPU时间片轮询自旋锁,直到获取为止,一般应用于加锁时间很短(1ms左右或更低)的场景。(主要是能够减少CPU上下文的切换)
这可以避免进程因被识别为“资源不足”而被操作系统置入休眠队列,从而避免不必要的上下文切换开销;但缺点是,它会导致“申请不到锁时执行死循环”,使得CPU核心占用100%——如果是单核单线程CPU,它就白白发一个时间片的热然后失去执行权(因为它占用了时间片,导致能释放资源给它的进/线程压根得不到执行机会);只有在多CPU和/或多核和/或多线程硬件平台上、且这个锁一定会在远短于一个时间片的时间内被请求到,它才可能真正提高效率(否则又是白白浪费时间、电力让CPU发热了)。
互斥锁:无法获取琐时,进线程立刻放弃剩余的时间片并进入阻塞(或者说挂起)状态,同时保存寄存器和程序计数器的内容(保存现场,上下文切换的前半部分),当可以获取锁时,进线程激活,等待被调度进CPU并恢复现场(上下文切换下半部分)
上下文切换会带来数十微秒的开销,不要在性能敏感的地方用互斥锁
读写锁:写锁被占用时,所有申请读锁和写锁的进线程都会被阻塞,读锁被占用时,申请写锁的进线程被阻塞,其他申请读锁的进线程不会。
条件变量:....
怎么进行选择呢?
进行压测吧....
https://blog.csdn.net/ojshilu/article/details/25244389
读写锁:有读者和写者两组进程,共享一个文件。1.允许多个读者对文件进行操作 2.同一时刻只能有一个写者
分析:读者与写者之间是互斥的。写者之间也是互斥的。这里实现的关键是有一个互斥访问的计数器count。
1.使用信号量实现(可以参考王道考研上面的实现)
https://blog.csdn.net/shichao1470/article/details/89856443
调用pthread_cond_wait前需要先对互斥量mutex上锁,才能把&mutex传入pthread_cond_wait函数
在pthread_cond_wait函数内部,会首先对传入的mutex解锁
当等待的条件到来后,pthread_cond_wait函数内部在返回前会去锁住传入的mutex
线程安全,多个线程同时操作一个对象,在各种不同情况下,都不会造成不同的后果。
https://blog.csdn.net/xiarendeniao/article/details/7277709
https://blog.csdn.net/tennysonsky/article/details/45127125?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param
之前还看过一个更加通俗易懂的解释,不过暂时找不到了
参考陈硕的https://blog.csdn.net/Solstice/article/details/8547547
shared_ptr并不是100%的线程安全,因为他内部有两个对象,一个是引用计数,另外一个是引用的对象,所以没法进行原子操作。
结论是:shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别。分析见陈硕。 虽然通过原子操作解决了引用计数的计数的线程安全问题, 但是智能指针指向的对象的线程安全问题,智能指针没有做任何的保证。
引用
所以,结论是并不是线程安全的。
https://www.cnblogs.com/ztteng/p/3411738.html
对于多线程的支持,仅限于一下情况:
1.多个线程同时读同一个容器
2.多个线程操作不同的容器
若对同一个容器,有读也有写就需要自己保证了
需要程序员自己来控制,比如:线程A读容器某一项时,线程B正在移除该项。这会导致一下无法预知的错误。
通常的解决方式是用开销较小的临界区(CRITICAL_SECTION)来做同步。以下列方式同步基本上可以做到线程安全的容器(就是在有写操作的情况下仍能保证安全)。
1.每次调用容器的成员函数的期间需要锁定。
2.每个容器容器返回迭代器的生存期需要锁定。
3.每个容器在调用算法的执行期需要锁定。
C++STL容器部分操作时多线程不安全的。在多线程情景下要使用STL容器必须考虑到这种情景
根据这个模式 自己封装一个 thread safe 的vector。每次操作函数的时候 std::lock_guard
参考github上的
https://github.com/LWeiFreshaman/STL_thread/blob/master/vector_thread.h
https://github.com/Abbeychenxi/threadsafe-stl/blob/master/stack-pthread.cpp
https://blog.csdn.net/weixin_35695879/article/details/89530410
1)errno是一个整型变量,当系统调用和一些库函数发生错误时会通过设置errno的值来告诉调用者出了什么问题。
2)errno的有效值都是非零的。(这个manpage有个悖论,第二段中说,errno从来不能被设为0,而在第三段又说有些接口会将其设置为0)
3)errno在ISO C标准中定义的,它可能是一个宏并且不能被显示声明(也就是重新定义)。
4)errno是线程安全的,在一个线程中设置它,不会影响别的线程对它的使用。这一点很重要,不知道有没有像我之前有过这样的问题:看起来errno像是一个全局的变量,应该不是线程安全的吧?看了这个之后,就有答案了,errno是thread-local的,也就是每个线程都有一个。具体见https://blog.csdn.net/xiaofei0859/article/details/4939733,有详细说明errono为啥是线程安全的
4. errno使用的注意事项:
1)当使用系统调用和库函数时,除了函数的返回值,记得还要考虑errno的值啊。
2)并不是所有的系统调用活着库函数都会设置errno的值,如果你的程序对它有依赖,需要开发人员在接口错误处理中,手工设置。
Thread_Local是什么东西