【C编程基础】多线程编程


基础知识

1.基本概念

(1)线程,即轻量级进程(LWP:LightWeight Process),是程序执行流的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位。

(2)线程同步,就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合。

(3)线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。

2.三种基本状态

就绪状态,指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;

运行状态,指线程占有处理机正在运行;

阻塞状态,指线程在等待一个事件(如信号量),逻辑上不可执行。

3.进程和线程的关系

简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。

线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

4.线程同步互斥的4种方式

(1)临界区(Critical Section):适合一个进程内的多线程访问公共区域或代码段时使用

(2)互斥量 (Mutex):适合不同进程内多线程访问公共区域或代码段时使用,与临界区相似。

(3)事件(Event):通过线程间触发事件实现同步互斥

(4)信号量(Semaphore):与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理与操作系统中PV操作类似,先设置一个访问公共区域的线程最大连接数,每有一个线程访问共享区资源数就减一,直到资源数小于等于零。

#include <pthread.h>多线程函数

线程按照其调度者可以分为用户级线程和核心级线程两种 。用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持;

我们常用基本就是用户级线程,总结一下POSIX提供的用户级线程接口。

基本线程操作相关的函数:
1.线程的建立结束

函数说明
pthread_create() 创建线程开始运行相关线程函数,运行结束则线程退出
pthread_eixt() 因为exit()是用来结束进程的,所以则需要使用特定结束线程的函数
pthread_join() 挂起当前线程,用于阻塞式地等待线程结束,如果线程已结束则立即返回,0=成功
pthread_cancel() 发送终止信号给thread线程,成功返回0,但是成功并不意味着thread会终止
pthread_testcancel() 在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求.
pthread_setcancelstate() 设置本线程对Cancel信号的反应
pthread_setcanceltype() 设置取消状态 继续运行至下一个取消点再退出或者是立即执行取消动作
pthread_setcancel() 设置取消状态

2.线程的互斥和同步

函数说明
pthread_mutex_init() 互斥锁的初始化
pthread_mutex_lock() 锁定互斥锁,如果尝试锁定已经被上锁的互斥锁则阻塞至可用为止
pthread_mutex_trylock() 非阻塞的锁定互斥锁
pthread_mutex_unlock() 释放互斥锁
pthread_mutex_destory() 互斥锁销毁函数

3.使用信号量控制线程(默认无名信号量)

函数说明
sem_init(sem) 初始化一个定位在sem的匿名信号量
sem_wait() 把信号量减1操作,如果信号量的当前值为0则进入阻塞,为原子操作
sem_trywait() 如果信号量的当前值为0则返回错误而不是阻塞调用(errno=EAGAIN),其实是sem_wait()的非阻塞版本
sem_post() 给信号量的值加1,它是一个“原子操作”,即同时对同一个信号量做加1,操作的两个线程是不会冲突的
sem_getvalue(sval) 把sem指向的信号量当前值放置在sval指向的整数上
sem_destory(sem) 销毁由sem指向的匿名信号量

4.线程的基本属性配置

函数说明
pthread_attr_init() 初始化配置一个线程对象的属性,需要用pthread_attr_destroy函数去除已有属性
pthread_attr_setscope() 设置线程属性
pthread_attr_setschedparam() 设置线程优先级
pthread_attr_getschedparam() 获取线程优先级

注意:使用线程,编译时需要引用软件库lpthread,如下:

gcc -o thread thread.c -lpthread
/* thread.c */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define THREAD_NUMBER       3                 /*线程数*/
#define REPEAT_NUMBER       5                 /*每个线程中的小任务数*/
#define DELAY_TIME_LEVELS  10.0             /*小任务之间的最大时间间隔*/
//
void * thrd_func(void *arg) { 
    /* 线程函数例程 */
    int thrd_num = (long)arg;
    int delay_time = 0;
    int count = 0;
    printf("Thread %d is starting\n", thrd_num);
    for (count = 0; count < REPEAT_NUMBER; count++) {
        delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
        sleep(delay_time);
        printf("\tThread %d: job %d delay = %d\n",thrd_num, count, delay_time);
    }

    printf("Thread %d finished\n", thrd_num);
    pthread_exit(NULL);
}

int main(void) {
     pthread_t thread[THREAD_NUMBER];
     int no = 0, res;
     void * thrd_ret;
     srand(time(NULL));    
     for (no = 0; no < THREAD_NUMBER; no++) {
          /* 创建多线程 */
          res = pthread_create(&thread[no], NULL, thrd_func, (void*) (long) no);
          if (res != 0) {
               printf("Create thread %d failed\n", no);
               exit(res);
          }
     }

     printf("Create treads success\n Waiting for threads to finish...\n");
     for (no = 0; no < THREAD_NUMBER; no++) {
          /* 等待线程结束 */
          res = pthread_join(thread[no], &thrd_ret);
          if (!res) {
            printf("Thread %d joined\n", no);
          } else {
            printf("Thread %d join failed\n", no);
          }
     }
     return 0;        
}


例程中循环3次建立3条线程,并且使用pthread_join函数依次等待线程结束; 
线程中使用rand()获取随机值随机休眠5次,随意会出现后执行的线程先执行完成; 

可以看到,线程1先于线程0执行,但是pthread_join的调用时间顺序,先等待线程0执行; 
由于线程1已经早结束,所以线程0被pthread_join等到的时候,线程1已结束,就在等待到线程1时,直接返回;


执行结果如下:
Create treads success
 Waiting for threads to finish...
Thread 0 is starting
Thread 1 is starting
Thread 2 is starting
        Thread 0: job 0 delay = 3
        Thread 1: job 0 delay = 9
        Thread 2: job 0 delay = 10
        Thread 0: job 1 delay = 9
        Thread 1: job 1 delay = 4
        Thread 0: job 2 delay = 1
        Thread 2: job 1 delay = 7
        Thread 0: job 3 delay = 8
        Thread 1: job 2 delay = 9
        Thread 1: job 3 delay = 1
        Thread 2: job 2 delay = 9
        Thread 1: job 4 delay = 4
Thread 1 finished
        Thread 0: job 4 delay = 10
Thread 0 finished
Thread 0 joined
Thread 1 joined
        Thread 2: job 3 delay = 8
        Thread 2: job 4 delay = 4
Thread 2 finished
Thread 2 joined
基本线程建立示例

gcc -o thread_mutex thread_mutex.c -lpthread
/*thread_mutex.c*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
 
#define THREAD_NUMBER        3            /* 线程数 */
#define REPEAT_NUMBER        3            /* 每个线程的小任务数 */
#define DELAY_TIME_LEVELS 10.0         /*小任务之间的最大时间间隔*/
pthread_mutex_t mutex;
 
void *thrd_func(void *arg) {
     int thrd_num = (int)(long) arg;
     int delay_time = 0, count = 0;
     int res;
     /* 互斥锁上锁 */
     res = pthread_mutex_lock(&mutex);
     if (res) {
          printf("Thread %d lock failed\n", thrd_num);
          pthread_exit(NULL);
     }
     printf("Thread %d lock \n", thrd_num);
     printf("Thread %d is starting\n", thrd_num);
     for (count = 0; count < REPEAT_NUMBER; count++) {          
         delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
         sleep(delay_time);
         printf("\tThread %d: job %d delay = %d\n", 
                                      thrd_num, count, delay_time);
     }
     printf("Thread %d finished\n", thrd_num);
     res = pthread_mutex_unlock(&mutex);
     if (res) {
          printf("Thread %d unlock failed\n", thrd_num);
          pthread_exit(NULL);
     }
     printf("Thread %d unlock\n", thrd_num);

     pthread_exit(NULL);
}
 
int main(void) {
     pthread_t thread[THREAD_NUMBER];
     int no = 0, res;
     void * thrd_ret;
 
     srand(time(NULL));
     /* 互斥锁初始化 */
     pthread_mutex_init(&mutex, NULL);
     for (no = 0; no < THREAD_NUMBER; no++) {
          res = pthread_create(&thread[no], NULL, thrd_func, (void*)(long)no);
          if (res != 0) {
              printf("Create thread %d failed\n", no);
              exit(res);
          }
     }     
     printf("Create treads success\n Waiting for threads to finish...\n");
     for (no = 0; no < THREAD_NUMBER; no++) {
          res = pthread_join(thread[no], &thrd_ret);
          if (!res) {
                printf("Thread %d joined\n", no);
          } else  {
              printf("Thread %d join failed\n", no);
          }
     }   
     /****互斥锁解锁***/
     pthread_mutex_destroy(&mutex);          
     return 0;        
}


添加同步锁pthread_mutex_t
在线程中加入,于是程序在执行线程程序时; 
调用pthread_mutex_lock上锁,发现上锁时候后进入等待,等待锁再次释放后重新上锁; 
所以线程程序加载到队列中等待,等待成功上锁后继续执行程序代码;

运行结果:
Create treads success
 Waiting for threads to finish...
Thread 2 lock 
Thread 2 is starting
        Thread 2: job 0 delay = 7
        Thread 2: job 1 delay = 2
        Thread 2: job 2 delay = 1
Thread 2 finished
Thread 2 unlock
Thread 1 lock 
Thread 1 is starting
        Thread 1: job 0 delay = 2
        Thread 1: job 1 delay = 6
        Thread 1: job 2 delay = 8
Thread 1 finished
Thread 1 unlock
Thread 0 lock 
Thread 0 is starting
        Thread 0: job 0 delay = 7
        Thread 0: job 1 delay = 1
        Thread 0: job 2 delay = 8
Thread 0 finished
Thread 0 unlock
Thread 0 joined
Thread 1 joined
Thread 2 joined
实现线程同步(互斥量)

1.创建线程

int pthread_create(pthread_t *restrict_ptid,const pthread_attr_t *restrict_attr,void *(*start_routine)(void*), void *restrict_arg);

(1)参数说明:

  • ptid是一个pthread_t *类型的指针,pthread_t是类似pid_t的数据结构,表示线程ID;
  • attr指明线程创建属性,如果为NULL就使用系统默认属性;
  • start_routine是线程的主函数,它的参数是void *类型的指针,返回值也是void *类型的指针;
  • arg是线程创建者传递给新建线程的参数,也就是start_routine的参数,如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为restrict_arg的参数传入。

(2)返回值:

若线程创建成功,则返回0。若线程创建失败,则返回出错编号

(3)注意事项:

线程创建者和新建线程之间没有fork()调用那样的父子关系,它们是对等关系。调用pthread_create()创建线程后,线程创建者和新建线程哪个先运行是不确定的,特别是在多处理机器上。

2.终止线程

void pthread_exit(void *value_ptr);

(1)参数说明:value_ptr作为线程的返回值被调用pthread_join的线程使用。

(2)注意事项:由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。


3.pthread_self():该函数返回调用线程的ID.这个数值与调用 pthread_create 创建本线程时使用的*thread 参数返回的值是一样的。
  注意:Thread IDs 仅仅保证在一个进程内部的唯一性。当一个结束了的线程 joined(使用join等待一个线程结束)之后, 或者一个detached 状态的线程被结束 thread ID可能会被重新使用。 pthread_self()返回的线程ID与 调用 gettid()得到的内核线程ID是不一样的。


4.pthread_equal():比较线程ID,线程ID的大小没有意义。
  引入原因:在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读,而应该使用pthread_equal来判断。


3.取消线程
int pthread_cancel(pthread_t thread);
  注意:若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL指令后,使用 pthread_join函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。

4.连接线程(阻塞)
int pthread_join(pthread_t thread, void **value_ptr);
  等待线程thread结束,并设置*value_ptr为thread的返回值。pthread_join阻塞调用者,一直到线程thread结束为止。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
  线程终止有一下几种方法:
  1.从主函数返回;
  2.自己调用pthread_exit();
  3.其他线程调用pthread_cancel();
  4.线程所属的进程中任何线程调用exit()导致所有线程结束。

5.分离线程
int pthread_detach(pthread_t thread);
  分离线程的语意是,线程thread结束后系统可以回收它的私有数据。
  注释:pthread有两种状态joinable状态和unjoinable状态 一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己,如:pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)结束相应子进程。

参考文档:

多线程函数的使用总结

pThreads线程(一) 基本API

线程同步互斥的4种方式

pThreads线程(一) 基本API

优质内容筛选与推荐>>
1、韩政府公布虚拟货币投机行为打击对策
2、转:安装完QQ必须要删除掉的几个恐怖文件
3、让人工智能发明自己的语言:OpenAI语言理解研究新方向
4、卡卡卡!小萝莉告诉你开发iOS应用如何避免卡顿
5、Python3下实现腾讯人工智能API调用


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn