线程 总结

前几天 组内学长讲了 一下线程的内容,现在 结合自己的理解进行 一下 总结

概念 :

线程是进程的一个实体,是 cpu 调度和分派的基本单位,被包含在进程之中,是进程的实际运作单位,自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器 , 寄存器 和 栈),但它可与用书一个进程的其他线程共享进程所拥有的全部资源.

和进程的差别

  1. 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在一般情况下不会对其他进程产生影响,而线程只是进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程崩溃掉就等于整个进程崩溃掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差
    一些。但对某些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程.
  2. 当一个父进程死亡时,他的 子进程可能不会死亡.
    但 一个主线程死亡时,他的 子线程一定会死亡
    例 :
#include<stdio.h>                                                                                                                                             
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

int *thread(void *arg)
{
        pthread_t newthid;

        newthid = pthread_self();
        printf( "this is a new thread,thread ID = %lu\n",newthid);

        return NULL;
}
int main(void)
{
        pthread_t thid;

        printf( "mian thread ID is %lu\n",pthread_self());  //打印主线程ID
        if(pthread_create(&thid,NULL,(void*)thread,NULL) != 0)
        {
                printf( "thread creation failed\n");
                exit(1);
        }
//      sleep(1);
        exit(0);
}

输出结果 :
输出结果
而将 sleep 的注释去掉后 :
输出结果 :
输出结果
sleep 的作用就是 让主线程 沉睡 一秒,避免主线程过早死亡

gettid 和 pthread_self() 的区别

例:

#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>


void *f(void *arg);
int main()                                                                                                                                                    
{
        pid_t pid1;
        pthread_t tid1;
        pthread_t tid2;
        int a;
        pid1 = fork();
        if(pid1 == 0)
        {
                a = 0;
                printf(" 子进程主线程 ID 为 %ld\n",pthread_self());
                pthread_create(&tid1,NULL,f,(void *)&a);
        }
        else 
        {
                a = 1;
                printf(" 父进程主线程 ID 为 %ld\n",pthread_self());
                pthread_create(&tid2,NULL,f,(void *)&a);
        }

        sleep(1);

}

void *f(void *arg) 
{
        if(*(int *)arg)
                printf(" 子进程所属线程 ID 为 %ld\n",pthread_self());
        else
                printf(" 父进程所属线程 ID 为 %ld\n",pthread_self());
    
        pthread_exit(NULL);
}

输出结果 :
输出结果
结论 :
pthread_self() 返回的是相对于进程中各个线程之间的标识号,对于这个进程内是唯一的,而不同进程中,每个线程的 pthread_self() 可能返回是一样的。要想得到内核中的真实线程 ID ,需调用 gettid(), 用户态的库并没有直接提供 gettid() 函数,需要执行类似的系统调用 syscall(__NR_gettid).

取消线程

发送终止信号给 thread 线程 , 发送成功并不意味着 thread 会终止 , 具体方式取决于线程属性

线程同步

为什么要有线程同步 ?

#include<stdio.h>                                                               
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

int a = 0;
void *f(void *arg) 
{
        for(int i = 0;i < 10000;++i)
        ++a;
        pthread_exit(NULL);
}
int main()
{
        pthread_t tid1,tid2;
        pthread_create(&tid1,NULL,f,NULL);
        pthread_create(&tid2,NULL,f,NULL);

        sleep(2);
        printf( "a = %d\n",a);
}

输出结果 :
输出结果
从结果可以看出 : a 不一定每次都是 20000.
a = 0 ;
因为 当线程 1 进行 多次 加法操作 ,但还没写进 内存中 ,cpu 切换到了线程 2,线程 2从内存中读的值还是 a = 0;然后累加,写进内存, cpu 切换到了线程 1,线程1 将 a 的值写入内存, 然后就将 线程 2 的数据覆盖了.所以最后 值会偏小.

所以 出现了线程同步.而 为了 实现线程同步 就出现了 互斥锁

互斥锁的特点

· 原子性:把一个互斥锁定义为一个原子操作,这意味着操作系统保证了如果一个线程锁定了互斥锁,则没有其他线程
        可以在同一时间成功锁定这个互斥量。
· 唯一性:如果一个线程锁定一个互斥量,在它接除锁定之前,没有其他线程可以锁定这个互斥量。

· 非繁忙等待:如果一个线程已经锁定了一个互斥锁,第二个线程又试图去锁定这个互斥锁,则第二个线程将
        被挂起(不占用CPU 资源),直到第一个线程解锁,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

死锁

使用锁操作不当 最可能的原因就是造成了死锁
例:

pthread_mutex_t mutex;
void foo() 
{
	pthread_mutex_lock(&mutex);    //死锁
	// do something
	pthread_mutex_unlock(&mutex);
}
void bar() 
{
	pthread_mutex_lock(&mutex);
	// do something
	foo();
	pthread_mutex_unlock(&mutex);
}
int main()
{
	pthread_mutex_init(&mutex);
	bar();
	return 0;
}

如试例 :
bar() 函数 调用 foo() ,foo()函数 又进行加锁,然而,在bar()函数已经加过锁了,并且没有解锁,就触发了非繁忙等待,在哪一直等下去.造成了死锁.

解决方案 : 而已使用 tyrlock() 函数.

条件变量

条件变量同锁一起使用使得线程可以以一种无竞争的方式等待任意条件的发生。所谓无竞争就是,条件改变这个信号会发送到所有等待这个信号的线程。而不是说一个线程接受到这个消息而其它线程就接收不到了。

而在条件变量这最重要 并且注意地方最多的 就是 pthread_cond_wait()

编程中,条件变量 通常都是和 互斥锁一起使用的.
为什么条件变量要和互斥锁一起使用呢 ?

#include<stdio.h>                                                                                                                                             
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *f(void *arg) 
{
		//pthread_mutex_lock(&mutex);
        pthread_cond_signal(&cond);
        //pthread_mutex_unlock(&mutex);
        pthread_exit(NULL);
}
int main()
{
        pthread_t tid1;
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);
    	//pthread_mutex_lock(&mutex);
        pthread_create(&tid1,NULL,f,(void *)&cond);
    
        usleep(10);
        pthread_cond_wait(&cond,&mutex);
    	//pthread_mutex_unlock(&mutex);
        return 0;
}

运行结果:

如图,程序阻塞了.
因为,子线程可能跑的比主线程 快,而如果子线程 先发送信号,但 主线程 还没跑到 接受信号的地方,这个信号自然是接受不到,然后主线程 会认为 子线程还没发送信号,就一直阻塞在 wait()出.

将注释去掉后 就是正确的操作了.
有一点要提的是 在 pthread_cond_wait() 内部 还有三个操作
           对 mutex 解锁 (1)
           //do something
           等待条件变量 cond 被唤醒 (2)
           //do something
           对 mutex 加锁 (3)

其中 (1)和(2)是 一步原子操作

原因

在主线程中加一个锁后,如果子线程跑的比主线程 快,就会阻塞在 子线程中加锁的地方等待,当 主线程到达接受信号的地方解锁并等待接收信号时,子线程 才开始发送信号.所以 主线程 一定能接受到信号.

注意

( 1 )在调用 pthread_cond_wait() 前必须加锁,防止因为多线程竞争而忽略唤醒信号

( 2 )调用 pthread_cond_signal 之后必须要立即释放互斥锁,因为 pthread_cond_wait 的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal 之后没有释放互斥锁 pthread_cond_wait 仍然要阻塞

参考博客 :
gettid() 和 preath_self()          https://blog.csdn.net/rsyp2008/article/details/45150621

取消线程                       https://www.cnblogs.com/lijunamneg/archive/2013/01/25/2877211.html

条件变量                       https://blog.csdn.net/brian12f/article/%20details/73882166

猜你喜欢

转载自blog.csdn.net/qq_43701555/article/details/98308627