得于斯者, 毁于斯
文章目录
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)
{
int* id = (int*)arg;
while(1)
{
pthread_mutex_lock(&mutex);
while(food == 0)
{
pthread_cond_wait(&laoba_cond, &mutex);
}
printf("老八[%d]号: 奥利给, 干了兄弟们!~~~~\n", id);
sleep(1);
food--;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&somepeople_cond);
}
return NULL;
}
void* make(void* arg)
{
int* id = (int*)arg;
while(1)
{
pthread_mutex_lock(&mutex);
while(food == 1)
{
pthread_cond_wait(&somepeople_cond, &mutex);
}
printf("无辜群众[%d]号: 噗~~~\n", id);
sleep(1);
food++;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&laoba_cond);
}
return NULL;
}
#define MAX_THREAD 3
int main()
{
int ret;
pthread_t laoba_tid[MAX_THREAD];
pthread_t somepeople_tid[MAX_THREAD];
// 初始化全局变量
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[i], NULL, eat, (void*)i);
if (ret != 0)
{
printf("pthread create error\n");
return -1;
}
}
for (i = 0; i < MAX_THREAD; i++)
{
ret = pthread_create(&somepeople_tid[i], NULL, make, (void*)i);
if (ret != 0)
{
printf("pthread create error\n");
return -1;
}
}
for (i = 0; i < MAX_THREAD; i++)
{
pthread_join(laoba_tid[i], NULL);
pthread_join(somepeople_tid[i], NULL);
}
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&laoba_cond);
pthread_cond_destroy(&somepeople_cond);
return 0;
}
- 运行结果
[test@localhost thread]$ ./cond
无辜群众[0]号: 噗~~~
老八[0]号: 奥利给, 干了兄弟们!~~~~
无辜群众[0]号: 噗~~~
老八[1]号: 奥利给, 干了兄弟们!~~~~
无辜群众[1]号: 噗~~~
老八[0]号: 奥利给, 干了兄弟们!~~~~
无辜群众[0]号: 噗~~~
老八[1]号: 奥利给, 干了兄弟们!~~~~
无辜群众[2]号: 噗~~~
老八[2]号: 奥利给, 干了兄弟们!~~~~
无辜群众[1]号: 噗~~~
老八[0]号: 奥利给, 干了兄弟们!~~~~
无辜群众[0]号: 噗~~~
老八[1]号: 奥利给, 干了兄弟们!~~~~
无辜群众[2]号: 噗~~~
老八[2]号: 奥利给, 干了兄弟们!~~~~
无辜群众[1]号: 噗~~~
老八[0]号: 奥利给, 干了兄弟们!~~~~
^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;
}
3.4 信号量和条件变量
- 信号量可以实现互斥量,大部分情况下也可以实现条件变量。甚至使用信号量的实现远比其他实现更容易理解。 然而很多时候使用信号量替换条件变量的可能会降低并发程序的性能。 也就是说, 虽然条件变量使用和理解上复杂, 但是它的并发性高于信号量. <得于斯毁于斯>
3.5 信号量和互斥锁
两者区别:
1. 互斥量用于线程的互斥,信号线用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
2. 互斥量值只能为0/1,信号量值可以为非负整数。
3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
4. 线程池
- 代码如下, 使用条件变量来同步线程, 互斥锁来保证任务队列的安全
#include <cstdio>
#include <queue>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
typedef void(*task_handler_t)(int);
class ThreadTask
{
public:
ThreadTask(int data, task_handler_t handler):
_data(data), _handler(handler){
}
void Run() {
return _handler(_data);//用传入的方法处理传入的数据
}
private:
int _data;//这是用户传入的要处理的数据
task_handler_t _handler;//这是用户传入的数据的处理方法
};
#define MAX_THREAD 5
class ThreadPool
{
public:
ThreadPool(int max_thread = MAX_THREAD):_thr_count(max_thread){
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_cond, NULL);
//sem_init(&sem, 0, 0);
for (int i = 0; i < _thr_count; i++) {
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_start, (void*)this);
if (ret != 0) {
printf("create thread error\n");
exit(0);
}
//若对线程的返回值并不关心,并且希望线程退出后能够自动释放资源
pthread_detach(tid); // 分离这个线程
}
}
~ThreadPool(){
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
//sem_destroy(&sem);
}
bool PushTask(const ThreadTask &task){
//像线程池外部提供的任务入队操作
//能够入队的其实都是生产者,因为没有做队列的最大节点限制,因此生产者不需要阻塞
//只需要保护task_queue的操作就可以
pthread_mutex_lock(&_mutex);
_task_queue.push(task);
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_cond);//唤醒线程池中的线程处理任务
//sem_post(&sem)
return true;
}
private:
//这是线程池中线程的入口函数
static void *thr_start(void *arg) {
//获取一个任务,然后调用任务对象的Run接口
//线程池中的线程并不会处理一个任务之后就退出,因此是一个死循环
ThreadPool *pool = (ThreadPool*)arg;
while(1) {
//判断队列是否位为NULL,则线程需要阻塞等待
pthread_mutex_lock(&pool->_mutex);
while (pool->_task_queue.empty()) {
pthread_cond_wait(&pool->_cond, &pool->_mutex);
}
//sem_wait()
//pthread_mutex_lock()
ThreadTask task = pool->_task_queue.front();//获取队首节点
pool->_task_queue.pop();//队首节点出队操作
pthread_mutex_unlock(&pool->_mutex);
// 解锁之后再进行任务处理,否则,会造成当前线程加锁获取任务进行处理期间
// 其它线程无法获取锁,导致无法处理任务---演变成为任务的串行化处理
// 并且加锁是为了保护task_queue的操作,而不是为了保护任务处理过程的
task.Run();// 使用任务中用户传入的处理函数处理传入的数据
}
return NULL;
}
private:
int _thr_count;//线程池中线程的数量
std::queue<ThreadTask> _task_queue;
pthread_mutex_t _mutex;//互斥保护_task_queue的操作
pthread_cond_t _cond;//线程池中线程等待的队列
};
void test(int data)//用户自己定义的数据处理函数
{
srand(time(NULL));
int sec = rand() % 5;
printf("thread:%p get data:%d sleep %d sec\n", pthread_self(), data, sec);
sleep(sec);
return;
}
int main()
{
ThreadPool pool;
for (int i = 0; i < 10; i++) {
ThreadTask task(i, test);
pool.PushTask(task);
}
while(1) {
sleep(1);
}
return 0;
}
完.