[Linux]多线程

十年热恋 提交于 2020-01-08 23:01:14

得于斯者, 毁于斯

1. Linux下线程概念

在这里插入图片描述

2. 线程控制

  • POSIX线程库
    与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的 要使用这些函数库,要通过引入头文<pthread.h> 链接这些线程函数库时要使用编译器命令的“-lpthread”选项

2.1 线程创建

在这里插入图片描述

  • tid作用
    在这里插入图片描述
  • 代码示例
    create.c
#include <stdio.h>                                                                                  
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
 
void* thread_start(void* arg)
{
     while(1)
     {
         printf("线程:%s\n", (char*)arg);
         sleep(1);
     }
     return NULL;
}
 
int main()
{
     pthread_t tid;
     char buf[] = "座中泣下谁最多, 江州司马青衫湿\n";
     int ret = pthread_create(&tid, NULL, thread_start, (void*)buf);
     if (ret != 0)
     {
         printf("thread create error:%d", ret);
         return -1;
     }
 
     while(1)
     {
         printf("I am main thread\n");
         sleep(1);
     }
 
     return 0;
}                       

makefile

  1 create:create.c                                                                                     
  2     gcc $^ -o $@ -lpthread #pthread
  • 在编译时, 要链接库函数pthread, 不使用l可以增强跨平台性, 两者均可
  • 结果
[test@localhost thread]$ ./create 
I am main thread
线程:座中泣下谁最多, 江州司马青衫湿

I am main thread
线程:座中泣下谁最多, 江州司马青衫湿

I am main thread
线程:座中泣下谁最多, 江州司马青衫湿

线程:座中泣下谁最多, 江州司马青衫湿

I am main thread
线程:座中泣下谁最多, 江州司马青衫湿
^C

使用ps -ef查看进程信息

[test@localhost ~]$  ps -ef | head -n 1 && ps -ef | grep create
UID         PID   PPID  C STIME TTY          TIME CMD
test       5343   2847  0 10:43 pts/0    00:00:00 ./create

使用 ps -efL 查看轻量级进程信息

UID         PID   PPID    LWP  C NLWP STIME TTY          TIME CMD
test       5343   2847   5343  0    2 10:43 pts/0    00:00:00 ./create
test       5343   2847   5344  0    2 10:43 pts/0    00:00:00 ./create

根据上面的结果, 可以看出, -L的选项是查看轻量级进程信息, 即线程信息
其中

名称 解释
UID user id 用户ID
PID process id 进程ID, 即线程组ID, 是主线程的ID
LWP light weight process or thread. 轻量级进程ID, 即线程标识符
NLWP number of LWP in the process. 线程数量
PPID parent processid, 父进程标识符

但是在PCB中的名称要注意区分
在PCB中: pid->轻量级进程ID, tgid->线程组ID, 主线程的pid

2.2 线程终止

在这里插入图片描述
在这里插入图片描述

2.3 线程等待

在这里插入图片描述

2.4 线程分离

在这里插入图片描述

3. 线程安全

3.1 线程安全的实现

在这里插入图片描述

3.2 实现互斥

在这里插入图片描述

  • 使用互斥锁实现黄牛抢票
#include <stdio.h>                                                                                    
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <pthread.h> 
 
int ticket = 100; // 设100张票, 属于临界资源
pthread_mutex_t mutex;
 
void *route(void *arg) 
{
     char *id = (char*)arg;
     while (1) 
     {
         pthread_mutex_lock(&mutex);
         if ( ticket > 0  ) 
         {
             usleep(1000);
             printf("%s sells ticket:%d\n", id, ticket);
             ticket--; 
             pthread_mutex_unlock(&mutex);
         } 
         else 
         { 
             pthread_mutex_unlock(&mutex);
             break;
         } 
         usleep(100);
     }
     return NULL;
}
 
int main() 
{  
     // 设四个黄牛
     pthread_t t1, t2, t3, t4;
     
     // 互斥锁的初始化
     // pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;使用宏初始化
     // 使用接口初始化
     pthread_mutex_init(&mutex, NULL);
 
     pthread_create(&t1, NULL, route, (void*)"thread 1"); 
     pthread_create(&t2, NULL, route, (void*)"thread 2");
     pthread_create(&t3, NULL, route, (void*)"thread 3");                                              
     pthread_create(&t4, NULL, route, (void*)"thread 4");
 
     pthread_join(t1, NULL);
     pthread_join(t2, NULL);
     pthread_join(t3, NULL);
     pthread_join(t4, NULL); 
 
     pthread_mutex_destroy(&mutex);
     return 0;
}                   
  • 结果
[test@localhost thread]$ ./mutex 
thread 4 sells ticket:100
thread 1 sells ticket:99
thread 2 sells ticket:98
thread 3 sells ticket:97
thread 4 sells ticket:96
thread 1 sells ticket:95
thread 2 sells ticket:94
thread 3 sells ticket:93
thread 4 sells ticket:92
thread 1 sells ticket:91
thread 2 sells ticket:90
...
...
...
thread 4 sells ticket:10
thread 1 sells ticket:9
thread 2 sells ticket:8
thread 3 sells ticket:7
thread 4 sells ticket:6
thread 1 sells ticket:5
thread 2 sells ticket:4
thread 3 sells ticket:3
thread 4 sells ticket:2
thread 1 sells ticket:1

3.3 实现同步

3.3.1 条件变量实现同步

  • 条件变量的接口函数
    在这里插入图片描述
  • 虚假唤醒
    唤醒操作(SetEvent和pthread_cond_signal)原本意图是唤醒一个等待的线程,但是在多核处理器下,可能会激活多个等待的线程,这种效应为“虚假唤醒”。linux帮助文档中提到:虽然虚假唤醒在pthread_cond_wait函数中可以解决,为了发生概率很低的情况而降低边缘条件(fringe condition)效率是不值得的,纠正这个问题会降低对所有基于它的所有更高级的同步操作的并发度。所以pthread_cond_wait的实现上没有去解决它。所以通常的解决办法是在线程被激活后还需要检测等待的条件是否满足,例如下图所示。
    在这里插入图片描述
    pthread_cond_wait中的while()不仅仅在等待条件变量前检查条件,实际上在等待条件变量后也检查条件。
  • 通过条件变量实现一个老八吃粑粑的多线程任务
    有三个老八线程, 三个无辜群众线程, 设food=0; 当food为1时, 群众不生产food,当food为0时, 才会生产food++. 唤醒老八
    对老八们来说, 只有一个用餐地点, 当food为1时, 一位老八可以用餐, 用完后food - -, 唤醒群众
  • 代码如下
#include <stdio.h>                                                                                  
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
 
pthread_cond_t laoba_cond;
pthread_cond_t somepeople_cond;
pthread_mutex_t mutex;
int food = 0;
 
 void* eat(void* arg)
{
     while(1)
     {
         pthread_mutex_lock(&mutex);
         while(food == 0)
         {
             pthread_cond_wait(&laoba_cond, &mutex);
         }
         printf("老八[%lu]号: 奥利给, 干了兄弟们!~~~~\n", pthread_self());
         food--;
         pthread_mutex_unlock(&mutex);
         pthread_cond_signal(&somepeople_cond);
     }
     return NULL;
}
 
 void* make(void* arg)
{
     while(1)
     { 
         pthread_mutex_lock(&mutex);
         while(food == 1)
         {
             pthread_cond_wait(&somepeople_cond, &mutex);
         }
         printf("无辜群众[%lu]号: 噗~~~\n", pthread_self());
         food++;
         pthread_mutex_unlock(&mutex); 
         pthread_cond_signal(&laoba_cond);
     }
     return NULL;
}

#define MAX_THREAD 3
int main()                                                                                          
{
     int ret;
     pthread_t laoba_tid[];
     pthread_t somepeople_tid;

     // 初始化全局变量
     pthread_cond_init(&laoba_cond, NULL);
     pthread_cond_init(&somepeople_cond, NULL);
     pthread_mutex_init(&mutex, NULL); 
 
     int i;
     for (i = 0; i < MAX_THREAD; i++)
     {
         ret = pthread_create(&laoba_tid, NULL, eat, NULL);
         if (ret != 0)
         {
             printf("pthread create error\n");
             return -1;
         }
     }
     for (i = 0; i < MAX_THREAD; i++)
     {
         ret = pthread_create(&somepeople_tid, NULL, make, NULL);
         if (ret != 0)                                                                               
         {
             printf("pthread create error\n");
             return -1;
         }
     }
 	 for (i = 0; i < MAX_THREAD; i++)
     {
     	pthread_join(laoba_tid, NULL);
     	pthread_join(somepeople_tid, NULL);
     }
     pthread_mutex_destroy(&mutex);
     pthread_cond_destroy(&laoba_cond);
     pthread_cond_destroy(&somepeople_cond);
     return 0;
}                             

  • 运行结果
无辜群众[140632672352000]号: 噗~~~
老八[140632689137408]号: 奥利给, 干了兄弟们!~~~~
无辜群众[140632680744704]号: 噗~~~
老八[140632697530112]号: 奥利给, 干了兄弟们!~~~~
无辜群众[140632663959296]号: 噗~~~
老八[140632705922816]号: 奥利给, 干了兄弟们!~~~~
无辜群众[140632672352000]号: 噗~~~
老八[140632689137408]号: 奥利给, 干了兄弟们!~~~~
无辜群众[140632680744704]号: 噗~~~
老八[140632697530112]号: 奥利给, 干了兄弟们!~~~~
无辜群众[140632663959296]号: 噗~~~
老八[140632705922816]号: 奥利给, 干了兄弟们!~~~~
无辜群众[140632672352000]号: 噗~~~
^C

3.3.2 信号量POSIX实现同步

  • 信号量才是我大汉正统
    信号量与操作系统的PV操作相同!!!
    在这里插入图片描述
    头文件: #include <semaphore.h>
  • 使用信号量实现生产者-消费者问题
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <pthread.h>
#include <semaphore.h>

#define MAX_QUEUE 5
class RingQueue
{
    public:
        RingQueue(int maxq = MAX_QUEUE):
            _capacity(maxq), _array(maxq), 
            _pos_read(0), _pos_write(0){
            sem_init(&_sem_data, 0, 0);//数据资源计数器
            sem_init(&_sem_space, 0, maxq);//空闲空间计数器
            sem_init(&_sem_lock, 0, 1);//锁的初始化
        }
        ~RingQueue(){
            sem_destroy(&_sem_data);
            sem_destroy(&_sem_space);
            sem_destroy(&_sem_lock);
        }
        bool Push(int &data){
            //没有空间空间则直接阻塞,并且空闲空间计数-1
            sem_wait(&_sem_space);//直接通过自身计数判断是否阻塞

            sem_wait(&_sem_lock);//加锁,保护入队操作
            _array[_pos_write] = data;
            _pos_write = (_pos_write + 1) % _capacity;
            sem_post(&_sem_lock);//解锁

            sem_post(&_sem_data);//数据资源计数+1,唤醒消费者 

            return true;
        }
        bool Pop(int *data) {
            sem_wait(&_sem_data);//通过资源计数判断是否能获取资源

            sem_wait(&_sem_lock);
            *data = _array[_pos_read];
            _pos_read = (_pos_read + 1) % _capacity;
            sem_post(&_sem_lock);

            sem_post(&_sem_space);
            return true;
        }
    private:
        std::vector<int> _array;
        int _capacity;
        int _pos_write;
        int _pos_read;
        sem_t _sem_space;
        sem_t _sem_data;
        sem_t _sem_lock;
};
#define MAX_THR 4

void *productor(void *arg)
{
    RingQueue *q = (RingQueue *)arg;
    int i = 0; 
    while(1) {
        //生产者入队数据
        q->Push(i);
        printf("productor %p --- put data:%d\n", 
                pthread_self(), i++);
    }
    return NULL;
}
void *consumer(void *arg)
{
    RingQueue *q = (RingQueue *)arg;
    while(1) {
        //消费者出队数据
        int data;
        q->Pop(&data);
        printf("consumer %p --- get data:%d\n",
                pthread_self(), data);
    }
    return NULL;
}
int main()
{
    int ret;
    pthread_t ptid[MAX_THR], ctid[MAX_THR];
    RingQueue q;

    for (int i = 0; i < MAX_THR; i++) {
        ret = pthread_create(&ptid[i], NULL, productor, (void*)&q);
        if (ret != 0) {
            printf("thread create error\n");
            return -1;
        }
    }
    for (int i = 0; i < MAX_THR; i++) {
        ret = pthread_create(&ctid[i], NULL, consumer, (void*)&q);
        if (ret != 0) {
            printf("thread create error\n");
            return -1;
        }
    }
    for (int i = 0; i < MAX_THR; i++) {
        pthread_join(ptid[i], NULL);
        pthread_join(ctid[i], NULL);
    }
    return 0;
}

4. 线程池

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!