多线程编程(Linux C)

您所在的位置:网站首页 多线程编程语言 多线程编程(Linux C)

多线程编程(Linux C)

2024-03-10 09:10| 来源: 网络整理| 查看: 265

多线程编程可以说每个程序员的基本功,同时也是开发中的难点之一,本文以Linux C为例,讲述了线程的创建及常用的几种线程同步的方式,最后对多线程编程进行了总结与思考并给出代码示例。

一、创建线程

多线程编程的第一步,创建线程。创建线程其实是增加了一个控制流程,使得同一进程中存在多个控制流程并发或者并行执行。

线程创建函数,其他函数这里不再列出,可以参考pthread.h。

#include int pthread_create( pthread_t *restrict thread, /*线程id*/ const pthread_attr_t *restrict attr, /*线程属性,默认可置为NULL,表示线程属性取缺省值*/ void *(*start_routine)(void*), /*线程入口函数*/ void *restrict arg /*线程入口函数的参数*/ );

代码示例:

#include #include #include #include #include char* thread_func1(void* arg) { pid_t pid = getpid(); pthread_t tid = pthread_self(); printf("%s pid: %u, tid: %u (0x%x)\n", (char*)arg, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid); char* msg = "thread_func1"; return msg; } void* thread_func2(void* arg) { pid_t pid = getpid(); pthread_t tid = pthread_self(); printf("%s pid: %u, tid: %u (0x%x)\n", (char*)arg, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid); char* msg = "thread_func2 "; while(1) { printf("%s running\n", msg); sleep(1); } return NULL; } int main() { pthread_t tid1, tid2; if (pthread_create(&tid1, NULL, (void*)thread_func1, "new thread:") != 0) { printf("pthread_create error."); exit(EXIT_FAILURE); } if (pthread_create(&tid2, NULL, (void*)thread_func2, "new thread:") != 0) { printf("pthread_create error."); exit(EXIT_FAILURE); } pthread_detach(tid2); char* rev = NULL; pthread_join(tid1, (void *)&rev); printf("%s return.\n", rev); pthread_cancel(tid2); printf("main thread end.\n"); return 0; } 二、线程同步

有时候我们需要多个线程相互协作来执行,这时需要线程间同步。线程间同步的常用方法有:

互斥 信号量 条件变量

我们先看一个未进行线程同步的示例:

#include #include #include #include #include #define LEN 100000 int num = 0; void* thread_func(void* arg) { for (int i = 0; i< LEN; ++i) { num += 1; } return NULL; } int main() { pthread_t tid1, tid2; pthread_create(&tid1, NULL, (void*)thread_func, NULL); pthread_create(&tid2, NULL, (void*)thread_func, NULL); char* rev = NULL; pthread_join(tid1, (void *)&rev); pthread_join(tid2, (void *)&rev); printf("correct result=%d, wrong result=%d.\n", 2*LEN, num); return 0; }

运行结果:correct result=200000, wrong result=106860.。

【1】互斥

这个是最容易理解的,在访问临界资源时,通过互斥,限制同一时刻最多只能有一个线程可以获取临界资源。

其实互斥的逻辑就是:如果访问临街资源发现没有其他线程上锁,就上锁,获取临界资源,期间如果其他线程执行到互斥锁发现已锁住,则线程挂起等待解锁,当前线程访问完临界资源后,解锁并唤醒其他被该互斥锁挂起的线程,等待再次被调度执行。

“挂起等待”和“唤醒等待线程”的操作如何实现?每个Mutex有一个等待队列,一个线程要在Mutex上挂起等待,首先在把自己加入等待队列中,然后置线程状态为睡眠,然后调用调度器函数切换到别的线程。一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行时就有可能切换到被唤醒的线程。

主要函数如下:

#include int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); /*初始化互斥量*/ int pthread_mutex_destroy(pthread_mutex_t *mutex); /*销毁互斥量*/ int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);

用互斥解决上面计算结果错误的问题,示例如下:

#include #include #include #include #include #define LEN 100000 int num = 0; void* thread_func(void* arg) { pthread_mutex_t* p_mutex = (pthread_mutex_t*)arg; for (int i = 0; i< LEN; ++i) { pthread_mutex_lock(p_mutex); num += 1; pthread_mutex_unlock(p_mutex); } return NULL; } int main() { pthread_mutex_t m_mutex; pthread_mutex_init(&m_mutex, NULL); pthread_t tid1, tid2; pthread_create(&tid1, NULL, (void*)thread_func, (void*)&m_mutex); pthread_create(&tid2, NULL, (void*)thread_func, (void*)&m_mutex); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_mutex_destroy(&m_mutex); printf("correct result=%d, result=%d.\n", 2*LEN, num); return 0; }

运行结果:correct result=200000, result=200000.

如果在互斥中还嵌套有其他互斥代码,需要注意死锁问题。

产生死锁的两种情况:

一种情况是:如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,产生死锁。 另一种典型的死锁情形是:线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了。

如何避免死锁:

不用互斥锁(这个很多时候很难办到) 写程序时应该尽量避免同时获得多个锁。 如果一定有必要这么做,则有一个原则:如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁,则不会出现死锁。 (比如一个程序中用到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1next = tmp; pthread_mutex_unlock(&mlock); pthread_cond_signal(&condv); count += n; if(count > LIMIT) { break; } sleep(rand()%5); } printf("producer count=%d\n", count); } void consumer(void* arg) { printf("consumer thread running.\n"); int count = 0; for(;;) { pthread_mutex_lock(&mlock); if (NULL == phead) { pthread_cond_wait(&condv, &mlock); } else { while(phead != NULL) { count += phead->n; struct data* tmp = phead; phead = phead->next; free(tmp); } } pthread_mutex_unlock(&mlock); if (count > LIMIT) break; } printf("consumer count=%d\n", count); } int main() { pthread_t tid1, tid2; pthread_create(&tid1, NULL, (void*)producer, NULL); pthread_create(&tid2, NULL, (void*)consumer, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; }

条件变量中的执行逻辑:

关键是理解执行到int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex) 这里时发生了什么,其他的都比较容易理解。执行这条函数前需要先获取互斥锁,判断条件是否满足,如果满足执行条件,则继续向下执行后释放锁;如果判断不满足执行条件,则释放锁,线程阻塞在这里,一直等到其他线程通知执行条件满足,唤醒线程,再次加锁,向下执行后释放锁。(简而言之就是:释放锁-->阻塞等待-->唤醒后加锁返回)

实现细节可看源码pthread_cond_wait.c和pthread_cond_signal.c

上面的例子可能有些繁琐,下面的这个代码示例则更为简洁:

#include #include #include #include #include #include #include #define NUM 3 pthread_cond_t condv = PTHREAD_COND_INITIALIZER; pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER; void producer(void* arg) { int n = NUM; while(n--) { sleep(1); pthread_cond_signal(&condv); printf("producer thread send notify signal. %d\t", NUM-n); } } void consumer(void* arg) { int n = 0; while (1) { pthread_cond_wait(&condv, &mlock); printf("recv producer thread notify signal. %d\n", ++n); if (NUM == n) { break; } } } int main() { pthread_t tid1, tid2; pthread_create(&tid1, NULL, (void*)producer, NULL); pthread_create(&tid2, NULL, (void*)consumer, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; }

运行结果:

producer thread send notify signal. 1 recv producer thread notify signal. 1 producer thread send notify signal. 2 recv producer thread notify signal. 2 producer thread send notify signal. 3 recv producer thread notify signal. 3 【3】信号量

信号量适用于控制一个仅支持有限个用户的共享资源。用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待时,该计数值减一;当线程完成一次对semaphore对象的释放时,计数值加一。当计数值为0时,线程挂起等待,直到计数值超过0.

主要函数如下:

#include int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_post(sem_t * sem); int sem_destroy(sem_t * sem);

代码示例如下:

#include #include #include #include #include #include #include #include #define NUM 5 int queue[NUM]; sem_t psem, csem; void producer(void* arg) { int pos = 0; int num, count = 0; for (int i=0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3