线程的相关知识

来源:互联网 发布:mac 下面菜单栏不见了 编辑:程序博客网 时间:2024/05/21 09:51

线程

  1. 线程的概念

     线程就是进程内部的一条执行路径或执行序列,也可以称之为执行流。每一个进程至少 有一条线程,我们称其为主线程,或者称之为执行线程。从代码角度看,即就是 main 函数 的函数体。在主线程中通过系统调用函数创建其他线程(函数线程)。那么这几条线程会同 时向下运行。线程控制的难点在于多条线程的同步操作。
  2. 线程实现的几种方式

     内核级:线程的创建、控制、销毁都是由内核实现的,每个线程对内核都是可见 的。 用户级:用户通过线程库创建、控制、销毁线程,内核只能看到进程整体,而看 不到线程。 组合模型:介于内核级和用户级之间,用户态创建多个线程,内核看到的也是多 个,只是这是种 m:n 的对应关系。
  3. 特点

     在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。 (1)轻型实体 线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。 线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:     <1 线程状态。     <2 当线程不运行时,被保存的现场资源。     <3 一组执行堆栈。     <4 存放每个线程的局部变量主存区。     <5 访问同一个进程中的主存和其它资源。     用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。 (2)独立调度和分派的基本单位。 在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。 (3)可并发执行。 在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。 (4)共享进程资源。 在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
  4. 进程线程的区别

     1.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 2.线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 3.一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
  5. 相关函数
    <1 线程的创建 int pthread_create(pthread_t threadid,pthread_attr_t attr,void(start_routine)(void),void arg);

     第一个参数:线程号 第二个参数:有关设置线程的属性(如栈的大小) 第三个参数:所要执行的函数 注意:其类型是: void * pfun( void *arg) 第四个参数:给线程函数传的参数 返回值:当执行成功的时候 返回 0

    <2 线程的结束

     a. void pthread_exit(void *ptr);     参数是个空类型的指针保存的是线程退出以后的返回值 b. pthread_cancel(pthread_t pd);     pthread_cancel 调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(CancellationPoint)。     取消点是线程检查是否被取消并按照请求进行动作的一个位置. c. int pthread_join(pthread_t id,void ** thread_retval);)     以阻塞方式等待thread指定线程结束     第一个参数thread是需要等待的线程ID     第二个参数rval_ptr是用户定义的指针,用来                                                        存储被等待线程的返回值 tips: i:线程退出的函数:pthread_exit(void *retval); 这个函数和 exit 不同,这个函数仅仅结束线程,而进程不会退出。如果主线程退出,那么其他线程也会随之结束,因为主线程结束后会调用 exit 函数,致使进程退出,那么其他线程也就会结束运行。 如果在主线程结束之前调用 pthread_exit 函数,那么仅仅是主线程退出,进程不会退出,其他线程会继续运行,直到最后一个线程退出为止。 ii:等待线程结束,获取线程退出状态的函数:pthread_join(pthread_t id, void **thread_return); 如果在任何一个线程中调用此函数, 这个线程就会阻塞运行, 直到 id 指定的线程退出, 并且可以通过 thread_return 参数获取其退出信息。这个退出信息由 pthread_exit 函数指定。
  6. linux 上 pthread 线程库的使用

    线程库使用方法如下代码所示: #include <stdio.h> #include <stdlib.h> #include <unistd.h>     #include <string.h> #include <pthread.h> #include <assert.h> // 创建线程所使用的函数,即就是新线程的执行代码 void *fun(void *arg) {     int i = 0;     for(;i < 5; ++i)     {         printf("thread run\n");         sleep(1);     } } int main() {     pthread_t id;     int res = pthread_create(&id, NULL, fun, NULL); // 系统调用创建新线程     assert(res == 0);     int i = 0;     for(; i < 5; ++i)     {         printf("main run\n");         sleep(1);     }     pthread_join(&id);     wait(NULL);     exit(0); }
  7. 线程同步:信号量 互斥锁 条件变量
    信号量的使用(只能在线程间使用,是线程内部的局部信号量,与进程间通信的信号量不同)

     sem_init();初始化信号量,赋初值    sem_wait();等待信号量变的可用,也就是P操作   sem_post(); V操作 +1   sem_destroy();销毁信号量(注意与进程间通信中的信号量不一样)、
    #include <stdio.h>    #include <stdlib.h>    #include <unistd.h>    #include <string.h>    #include <pthread.h>    #include <semaphore.h>    #define MAX 1024    char work_area[MAX] = {0};    sem_t bin_sem;    void * pthread_funaction(void *);    void main()    {        pthread_t pthread;        sem_init(&bin_sem, 0, 0);        pthread_create(&pthread, NULL, pthread_funaction, NULL);        while(strncmp("end", work_area, 3) != 0)        {            gets(work_area);            sem_post(&bin_sem);        }        pthread_join(pthread, NULL);        sem_destroy(&bin_sem);    }    void *pthread_funaction(void *arg)    {        sleep(10);        sem_wait(&bin_sem);        while(strncmp("end", work_area, 3) != 0)        {            printf("you input %d char\n", strlen(work_area));            sem_wait(&bin_sem);        }        pthread_exit(NULL);    }

互斥锁的使用:
 pthread_mutex_init(); //初始化
 pthread_mutex_lock(); //加锁
 pthread_mutex_unlock() //解锁
 pthread_mutex_destroy() //销毁 

    #include<stdio.h>    #include<stdlib.h>    #include<assert.h>    #include<unistd.h>    #include<pthread.h>    pthread_mutex_t mutex;    void fun(void *arg)    {          pthread_mutex_init(&mutex,NULL);          pthread_mutex_lock(&mutex);          printf("pthread get mutex\n");          sleep(5);          pthread_mutex_unlock(&mutex);          printf("pthread free mutex\n");    }    int main()    {      int i = 0;      pthread_t id;      pthread_mutex_init(&mutex,NULL);      pthread_create(&id,NULL,fun,NULL);      sleep(2);      if(fork()==0)      {          printf("child will get mutex\n");          pthread_mutex_lock(&mutex);          printf("child get mutex\n");          sleep(1);          pthread_mutex_unlock(&mutex);          printf("child free mutex\n");      }      pthread_join(id,NULL);      wait(NULL);      pthread_mutex_destroy(&mutex);      exit(0);    }

问题:子线程会复制父进程此时的状态,包括它的锁,但不是同一把锁,因此该进程退出不了。

#include<stdio.h>#include<stdlib.h>#include<assert.h>#include<unistd.h>#include<pthread.h>pthread_mutex_t mutex;void fun(void *arg){      pthread_mutex_init(&mutex,NULL);      pthread_mutex_lock(&mutex);      printf("pthread get mutex\n");      sleep(5);      pthread_mutex_unlock(&mutex);      printf("pthread free mutex\n");}void prepare(){        pthread_mutex_lock(&mutex);}void infork(){        pthread_mutex_unlock(&mutex);}int main(){  int i = 0;  pthread_t id;  pthread_mutex_init(&mutex,NULL);  pthread_create(&id,NULL,fun,NULL);  sleep(2);  int res = pthread_atfork(prepare,infork,infork);  if(res == 0)  {      printf("child will get mutex\n");      pthread_mutex_lock(&mutex);      printf("child get mutex\n");      sleep(1);      pthread_mutex_unlock(&mutex);      printf("child free mutex\n");  }  else  {      wait(NULL);  }  pthread_join(id,NULL);  pthread_mutex_destroy(&mutex);  exit(0);}解决:采用pthread_atfork 函数,pthread_atfork()在fork()之前调用,当调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程成功后,父进程会调用parent ,子进程会调用child。(即在fork之前加锁,在成功之后父子进程解锁,保证fork之前父进程没有锁)

tips:

1.多线程执行fork一个进程时,只执行fork所在线程的执行路径上的事情,2.如果父进程中用了锁或信号量时,而fork出来的子进程会复制时父进程的锁的状态,即父子进程各有一把锁,且子进程中的锁的状态时复制那一刻的父进程锁的状态,但我们无法得到该锁的状态,所以需要用一个函数atfork()来等到父进程放开锁时进行fork复制,此时复制出的子进程的锁的状态就是放开锁的状态。

8.线程安全
例子:主线程切割字符串“a b c d e f g h ”,子线程strtok():"1 2 3 4 5 6 7 8 9"

Tips:strtok 函数:char *strtok(char *str, const char *delim) 使用了静态变量  注:函数内部使用静态变量或者访问了全局变量,非线程安全的(不可重入函数)    解决:线程安全函数:char *strtok_r(char *str, const char *delim, char **saveptr); 第一个参数为最先传入字符串的地址 第二个参数为分割字符,第三个参数为记录当前线程分割的地址
原创粉丝点击